2011年4月3日日曜日

#appengine 1.4.3の #slim3 単体テスト環境でQueueへのAddで例外が発生する場合

追記

このエントリを書いた数時間後に、この問題に対する対策が実施されたslim3-1.0.10がリリースされています。このエントリは無視してそれを使いましょう!

Slim3の単体テスト環境で、先週リリースされた Google App Engine SDK 1.4.3を適用するとQueueへTaskをaddする処理で次のような例外が発生します。

java.lang.IllegalArgumentException: Task name does not match expression [a-zA-Z\d-]{1,500}; given taskname: 'null'
 at com.google.appengine.api.taskqueue.TaskHandle.validateTaskName(TaskHandle.java:103)
 at com.google.appengine.api.taskqueue.TaskHandle.(TaskHandle.java:30)
 at com.google.appengine.api.taskqueue.QueueImpl.add(QueueImpl.java:489)

あれこれ見てみた結果、この原因は、1.4.3 からSDK内でtaskqueue#BulkAddのresponseをチェックするようになった、という事かなと思います。

対処方法

Slim3内部でなんとかするしか無いですが、急いでいる方は次のようにすると応急処置になります。

  1. Slim3を使ったプロジェクトに、org.slim3.tester.AppEngineTesterを作成する
  2. Slim3のorg.slim3.tester.AppEngineTesterをそのクラスにまるごとコピーする
  3. 次の箇所を探す。
    } else if (service.equals(TASKQUEUE_SERVICE) && method.equals(BULK_ADD_METHOD)) {
      TaskQueueBulkAddRequest taskPb = new TaskQueueBulkAddRequest();
      taskPb.mergeFrom(requestBuf);
      TaskQueueBulkAddResponse responsePb = new TaskQueueBulkAddResponse();
      for (int i = 0; i < taskPb.addRequestSize(); i++) {
        tasks.add(taskPb.getAddRequest(i));
        responsePb.addTaskResult();
      }
      return responsePb.toByteArray();
    
  4. その箇所を次のように変更する。
    } else if (service.equals(TASKQUEUE_SERVICE) && method.equals(BULK_ADD_METHOD)) {
      TaskQueueBulkAddRequest taskPb = new TaskQueueBulkAddRequest();
      taskPb.mergeFrom(requestBuf);
      TaskQueueBulkAddResponse responsePb = new TaskQueueBulkAddResponse();
      for (int i = 0; i < taskPb.addRequestSize(); i++) {
        tasks.add(taskPb.getAddRequest(i));
        TaskResult taskResult = new TaskResult();
        taskResult.setChosenTaskName("task" + String.valueOf(System.nanoTime()));
        responsePb.addTaskResult(taskResult);
        // responsePb.addTaskResult();
      }
      return responsePb.toByteArray();

ついでに

どうせSlim3のクラスにパッチをあてるなら…という事で、テスト時の初期ファイルをSlim3の単体テストで使用してテストの処理時間を短縮したい、等考えている人は次の対処をしておくと便利かもしれません。

AppEngineTester#tearDown()内のApiProxy.setDelegate(originalDelegate);となっている箇所の後ろに次の処理を追加する。

if (!AppEngineUtil.isProduction()) {
  ClassLoader loader = loadLibraries();
  Class apiProxyLocalImplClass = loader.loadClass(API_PROXY_LOCAL_IMPL_CLASS_NAME);
  Method stopMethod = apiProxyLocalImplClass.getMethod("stop");
  stopMethod.setAccessible(true);
  stopMethod.invoke(apiProxyLocalImpl);
  ApiProxy.setEnvironmentForCurrentThread(originalEnvironment);
  new File("build/test-classes/WEB-INF/appengine-generated/local_db.bin").delete();
}   

単体テスト用の初期データファイルをSlim3の単体テスト実行フォルダ(build/test-classes/WEB-INF/)にコピーしてテストする際は、データストアを正しく終了する処理をしておかないとデータファイルが正しく読めなくなってくる(タイミングによっては問題なく読める時もあるw)ための対処です。念には念を入れてlocal_db.binの削除も行っています。この対策を行って、AppEngineTester#setUp()の前に初期データファイルを"build/test-classes/WEB-INF/appengine-generated/local_db.binへコピーしておけばテストのための初期データ作成が高速になります。

コメントを投稿