workers: Put all tests in bazel_worker_test.sh. Add a testbed that makes it easy to test details of how the worker strategy behaves in corner cases.

--
MOS_MIGRATED_REVID=103541927
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerFactory.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerFactory.java
index 208b88c..7da7014 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerFactory.java
@@ -76,15 +76,6 @@
    */
   @Override
   public boolean validateObject(WorkerKey key, PooledObject<Worker> p) {
-    if (verbose) {
-      reporter.handle(
-          Event.info(
-              "Validating "
-                  + key.getMnemonic()
-                  + " worker (id "
-                  + p.getObject().getWorkerId()
-                  + ")."));
-    }
     return p.getObject().isAlive();
   }
 }
diff --git a/src/test/java/BUILD b/src/test/java/BUILD
index 9f3ead7..da59f46 100644
--- a/src/test/java/BUILD
+++ b/src/test/java/BUILD
@@ -603,6 +603,30 @@
     ],
 )
 
+java_library(
+    name = "worker-example-lib",
+    srcs = glob(["com/google/devtools/build/lib/worker/ExampleWorker*.java"]),
+    visibility = [
+        "//src/test/shell/bazel:__pkg__",
+    ],
+    deps = [
+        "//src/main/java:options",
+        "//src/main/protobuf:proto_worker_protocol",
+        "//third_party:guava",
+    ],
+)
+
+java_binary(
+    name = "worker-example",
+    main_class = "com.google.devtools.build.lib.worker.ExampleWorker",
+    visibility = [
+        "//src/test/shell/bazel:__pkg__",
+    ],
+    runtime_deps = [
+        ":worker-example-lib",
+    ],
+)
+
 TEST_SUITES = [
     "ziputils",
     "rules",
diff --git a/src/test/java/com/google/devtools/build/lib/worker/ExampleWorker.java b/src/test/java/com/google/devtools/build/lib/worker/ExampleWorker.java
new file mode 100644
index 0000000..b9146b2
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/worker/ExampleWorker.java
@@ -0,0 +1,128 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.worker;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.worker.ExampleWorkerOptions.ExampleWorkOptions;
+import com.google.devtools.build.lib.worker.WorkerProtocol.WorkRequest;
+import com.google.devtools.build.lib.worker.WorkerProtocol.WorkResponse;
+import com.google.devtools.common.options.OptionsParser;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An example implementation of a worker process that is used for integration tests.
+ */
+public class ExampleWorker {
+
+  public static void main(String[] args) throws Exception {
+    if (ImmutableSet.copyOf(args).contains("--persistent_worker")) {
+      OptionsParser parser = OptionsParser.newOptionsParser(ExampleWorkerOptions.class);
+      parser.setAllowResidue(false);
+      parser.parse(args);
+      ExampleWorkerOptions workerOptions = parser.getOptions(ExampleWorkerOptions.class);
+      Preconditions.checkState(workerOptions.persistentWorker);
+
+      runPersistentWorker(workerOptions);
+    } else {
+      // This is a single invocation of the example that exits after it processed the request.
+      processRequest(ImmutableList.copyOf(args));
+    }
+  }
+
+  private static void runPersistentWorker(ExampleWorkerOptions workerOptions) throws IOException {
+    PrintStream originalStdOut = System.out;
+    PrintStream originalStdErr = System.err;
+
+    while (true) {
+      try {
+        WorkRequest request = WorkRequest.parseDelimitedFrom(System.in);
+        if (request == null) {
+          break;
+        }
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        int exitCode = 0;
+
+        try (PrintStream ps = new PrintStream(baos)) {
+          System.setOut(ps);
+          System.setErr(ps);
+
+          try {
+            processRequest(request.getArgumentsList());
+          } catch (Exception e) {
+            e.printStackTrace();
+            exitCode = 1;
+          }
+        } finally {
+          System.setOut(originalStdOut);
+          System.setErr(originalStdErr);
+        }
+
+        WorkResponse.newBuilder()
+            .setOutput(baos.toString())
+            .setExitCode(exitCode)
+            .build()
+            .writeDelimitedTo(System.out);
+        System.out.flush();
+      } finally {
+        // Be a good worker process and consume less memory when idle.
+        System.gc();
+      }
+    }
+  }
+
+  private static void processRequest(List<String> args) throws Exception {
+    if (args.size() == 1 && args.get(0).startsWith("@")) {
+      args = Files.readAllLines(Paths.get(args.get(0).substring(1)), UTF_8);
+    }
+
+    OptionsParser parser = OptionsParser.newOptionsParser(ExampleWorkOptions.class);
+    parser.setAllowResidue(true);
+    parser.parse(args);
+    ExampleWorkOptions workOptions = parser.getOptions(ExampleWorkOptions.class);
+
+    List<String> residue = parser.getResidue();
+    List<String> outputs = new ArrayList(residue.size());
+    for (String arg : residue) {
+      String output = arg;
+      if (workOptions.uppercase) {
+        output = output.toUpperCase();
+      }
+      outputs.add(output);
+    }
+
+    String outputStr = Joiner.on(' ').join(outputs);
+    if (workOptions.outputFile.isEmpty()) {
+      System.err.println("ExampleWorker: Writing to stdout!");
+      System.out.println(outputStr);
+    } else {
+      System.err.println("ExampleWorker: Writing to file " + workOptions.outputFile);
+      try (PrintStream outputFile = new PrintStream(workOptions.outputFile)) {
+        outputFile.println(outputStr);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/worker/ExampleWorkerOptions.java b/src/test/java/com/google/devtools/build/lib/worker/ExampleWorkerOptions.java
new file mode 100644
index 0000000..3b5878c
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/worker/ExampleWorkerOptions.java
@@ -0,0 +1,41 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.worker;
+
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionsBase;
+
+/**
+ * Options for the example worker itself.
+ */
+public class ExampleWorkerOptions extends OptionsBase {
+
+  /**
+   * Options for the example worker concerning single units of work.
+   */
+  public static class ExampleWorkOptions extends OptionsBase {
+    @Option(
+      name = "output_file",
+      defaultValue = "",
+      help = "Write the output to a file instead of stdout."
+    )
+    public String outputFile;
+
+    @Option(name = "uppercase", defaultValue = "false", help = "Uppercase the input.")
+    public boolean uppercase;
+  }
+
+  @Option(name = "persistent_worker", defaultValue = "false")
+  public boolean persistentWorker;
+}
diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD
index 1b606b7..4df03b7 100644
--- a/src/test/shell/bazel/BUILD
+++ b/src/test/shell/bazel/BUILD
@@ -213,7 +213,10 @@
     name = "bazel_worker_test",
     size = "large",
     srcs = ["bazel_worker_test.sh"],
-    data = [":test-deps"],
+    data = [
+        ":test-deps",
+        "//src/test/java:worker-example_deploy.jar",
+    ],
 )
 
 sh_test(
diff --git a/src/test/shell/bazel/bazel_worker_test.sh b/src/test/shell/bazel/bazel_worker_test.sh
index 1652fe6..9367bb2 100755
--- a/src/test/shell/bazel/bazel_worker_test.sh
+++ b/src/test/shell/bazel/bazel_worker_test.sh
@@ -17,6 +17,11 @@
 # Test rules provided in Bazel not tested by examples
 #
 
+# TODO(philwo): Change this so the path to the custom worker gets passed in as an argument to the
+# test, once the bug that makes using the "args" attribute with sh_tests in Bazel impossible is
+# fixed.
+example_worker=$(find $PWD -name worker-example_deploy.jar)
+
 # Load test environment
 source $(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/test-setup.sh \
   || { echo "test-setup.sh not found!" >&2; exit 1; }
@@ -58,22 +63,164 @@
 EOF
 }
 
+function print_workers() {
+  pid=$(bazel info | fgrep server_pid | cut -d' ' -f2)
+  # DANGER. This contains arcane shell wizardry that was carefully crafted to be compatible with
+  # both BSD and GNU tools so that this works under Linux and OS X.
+  ps ax -o ppid,pid | awk '{$1=$1};1' | egrep "^${pid} " | cut -d' ' -f2
+}
+
+function shutdown_and_print_unkilled_workers() {
+  workers=$(print_workers)
+  bazel shutdown || fail "shutdown failed"
+  # Wait at most 10 seconds for all workers to shut down, then print the remaining (if any).
+  for i in 0 1 2 3 4 5 6 7 8 9; do
+    still_running_workers=$(for pid in $workers; do ps -p $pid | sed 1d; done)
+    if [[ ! -z "${still_running_workers}" ]]; then
+      sleep 1
+    fi
+  done
+}
+
+function assert_workers_running() {
+  workers=$(print_workers)
+  if [[ -z "${workers}" ]]; then
+    fail "Expected workers to be running, but found none"
+  fi
+}
+
+function assert_workers_not_running() {
+  workers=$(print_workers)
+  if [[ ! -z "${workers}" ]]; then
+    fail "Expected no workers, but found some running: ${workers}"
+  fi
+}
+
 function test_compiles_hello_library_using_persistent_javac() {
   write_hello_library_files
   bazel --batch clean
-  bazel build --experimental_persistent_javac //java/main:main || fail "build failed"
+
+  bazel build --strategy=Javac=worker //java/main:main || fail "build failed"
   bazel-bin/java/main/main | grep -q "Hello, Library!;Hello, World!" \
     || fail "comparison failed"
-  bazel_pid=$(bazel info | fgrep server_pid | cut -d' ' -f2)
-  # DANGER. This contains arcane shell wizardry that was carefully crafted to be compatible with
-  # both BSD and GNU tools so that this works under Linux and OS X.
-  bazel_children=$(ps ax -o ppid,pid | awk '{$1=$1};1' | egrep "^${bazel_pid} " | cut -d' ' -f2)
-  bazel shutdown || fail "shutdown failed"
-  sleep 10
-  unkilled_children=$(for pid in $bazel_children; do ps -p $pid | sed 1d; done)
-  if [ ! -z "$unkilled_children" ]; then
-    fail "Worker processes were still running: ${unkilled_children}"
+  assert_workers_running
+  unkilled_workers=$(shutdown_and_print_unkilled_workers)
+  if [ ! -z "$unkilled_workers" ]; then
+    fail "Worker processes were still running after shutdown: ${unkilled_workers}"
   fi
 }
 
+function test_incremental_heuristic() {
+  write_hello_library_files
+  bazel --batch clean
+
+  # Default strategy is assumed to not use workers.
+  bazel build //java/main:main || fail "build failed"
+  assert_workers_not_running
+
+  # No workers used, because too many files changed.
+  echo '// hello '>> java/hello_library/HelloLibrary.java
+  echo '// hello' >> java/main/Main.java
+  bazel build --worker_max_changed_files=1 --strategy=Javac=worker //java/main:main \
+    || fail "build failed"
+  assert_workers_not_running
+
+  # Workers used, because changed number of files is less-or-equal to --worker_max_changed_files=2.
+  echo '// again '>> java/hello_library/HelloLibrary.java
+  echo '// again' >> java/main/Main.java
+  bazel build --worker_max_changed_files=2 --strategy=Javac=worker //java/main:main \
+    || fail "build failed"
+  assert_workers_running
+}
+
+function test_workers_quit_after_build() {
+  write_hello_library_files
+  bazel --batch clean
+
+  bazel build --worker_quit_after_build --strategy=Javac=worker //java/main:main \
+    || fail "build failed"
+  assert_workers_not_running
+}
+
+function prepare_example_worker() {
+  cp -v ${example_worker} worker_lib.jar
+
+  cat >work.bzl <<'EOF'
+def _impl(ctx):
+  worker = ctx.executable.worker
+  output = ctx.outputs.out
+
+  # Generate the "@"-file containing the command-line args for the unit of work.
+  argfile = ctx.new_file(ctx.configuration.bin_dir, "worker_input")
+  ctx.file_action(output=argfile, content="\n".join(["--output_file=" + output.path] + ctx.attr.args))
+
+  ctx.action(
+      inputs=[argfile],
+      outputs=[output],
+      executable=worker,
+      progress_message="Working on %s" % ctx.label.name,
+      mnemonic="Work",
+      arguments=ctx.attr.worker_args + ["@" + argfile.path],
+  )
+
+work = rule(
+    implementation=_impl,
+    attrs={
+        "worker": attr.label(cfg=HOST_CFG, mandatory=True, allow_files=True, executable=True),
+        "worker_args": attr.string_list(),
+        "args": attr.string_list(),
+    },
+    outputs = {"out": "%{name}.out"},
+)
+EOF
+  cat >BUILD <<EOF
+load("work", "work")
+
+java_import(
+  name = "worker_lib",
+  jars = ["worker_lib.jar"],
+)
+
+java_binary(
+  name = "worker",
+  main_class = "com.google.devtools.build.lib.worker.ExampleWorker",
+  runtime_deps = [
+    ":worker_lib",
+  ],
+)
+
+EOF
+}
+
+function test_example_worker() {
+  prepare_example_worker
+
+  cat >>BUILD <<EOF
+work(
+  name = "hello_world",
+  worker = ":worker",
+  args = ["hello world"],
+)
+
+work(
+  name = "hello_world_uppercase",
+  worker = ":worker",
+  args = ["--uppercase", "hello world"],
+)
+EOF
+
+  bazel --batch clean
+  assert_workers_not_running
+
+  bazel build --strategy=Work=worker :hello_world \
+    || fail "build failed"
+  assert_equals "hello world" "$(cat bazel-bin/hello_world.out)"
+  assert_workers_running
+
+  bazel build --worker_quit_after_build --strategy=Work=worker :hello_world_uppercase \
+    || fail "build failed"
+  assert_equals "HELLO WORLD" "$(cat bazel-bin/hello_world_uppercase.out)"
+  assert_workers_not_running
+}
+
 run_suite "Worker integration tests"