Trivial skylark testing

--
MOS_MIGRATED_REVID=96404668
diff --git a/tools/build_rules/test_rules.bzl b/tools/build_rules/test_rules.bzl
new file mode 100644
index 0000000..efd81ee
--- /dev/null
+++ b/tools/build_rules/test_rules.bzl
@@ -0,0 +1,197 @@
+# 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.
+
+# This is a quick and dirty rule to make Bazel compile itself.  It
+# only supports Java.
+
+"""
+test_rules.bzl: Utilities for testing bazel
+"""
+
+### First, trivial tests that either always pass, always fail,
+### or pass depending on a trivial computation
+
+def success_target(ctx, msg):
+  """Return a success for an analysis test.
+
+The test rule must have an executable output.
+"""
+  exe = ctx.outputs.executable
+  dat = ctx.new_file(ctx.data_configuration.genfiles_dir, exe, ".dat")
+  ctx.file_action(
+      output = dat,
+      content = msg)
+  ctx.file_action(
+      output = exe,
+      content = "cat " + dat.path + " ; echo",
+      executable = True)
+  return struct(runfiles=ctx.runfiles([exe, dat]))
+
+def _successful_test_impl(ctx):
+  return success_target(ctx, ctx.attr.msg)
+
+successful_test = rule(
+    implementation=_successful_test_impl,
+    attrs={"msg": attr.string(mandatory=True)},
+    test=True, executable=True)
+
+
+def failure_target(ctx, msg):
+  """Return a failure for an analysis test.
+
+The test rule must have an executable output.
+"""
+  ### fail(msg) ### <--- This would fail at analysis time.
+  exe = ctx.outputs.executable
+  dat = ctx.new_file(ctx.data_configuration.genfiles_dir, exe, ".dat")
+  ctx.file_action(
+      output = dat,
+      content = msg)
+  ctx.file_action(
+      output = exe,
+      content = "(cat " + dat.short_path + " ; echo ) >&2 ; exit 1",
+      executable = True)
+  return struct(runfiles=ctx.runfiles([exe, dat]))
+
+def _failed_test_impl(ctx):
+  return failure_target(ctx, ctx.attr.msg)
+
+failed_test = rule(
+    implementation=_failed_test_impl,
+    attrs={"msg": attr.string(mandatory=True)},
+    test=True, executable=True)
+
+
+### Second, general purpose utilities
+
+def assert_(condition, string="assertion failed", *args):
+  """Trivial assertion mechanism.
+
+  Not quite compatible with python assert(): it takes % arguments."""
+
+  if not condition:
+    fail(string % args)
+
+
+def strip_prefix(prefix, string):
+  assert_(string.startswith(prefix),
+    "%s does not start with %s", string, prefix)
+  return string[len(prefix)+1:len(string)]
+
+
+def expectation_description(expect=None, expect_failure=None):
+  """Turn expectation of result or error into a string"""
+  if expect_failure:
+    return "failure " + str(expect_failure)
+  else:
+    return "result " + repr(expect)
+
+
+def check_results(result, failure_message, expect, expect_failure):
+  """See if actual computation matches expectation
+
+  return: a pair (tuple) of a boolean (true if success)
+  and a message (string)"""
+
+  wanted = expectation_description(expect, expect_failure)
+  found = expectation_description(result, failure_message)
+  if wanted == found:
+    return (True, "successfully computed " + wanted)
+  else:
+    return (False, "expect " + wanted + " but found " + found)
+
+
+def load_results(name, result=None, failure=None, expect=None, expect_failure=None):
+  """issue load-time success or failure of a test of given name based on results."""
+  (is_success, msg) = check_results(result, failure, expect, expect_failure)
+  this_test = successful_test if is_success else failed_test
+  return this_test(name=name, msg=msg)
+
+
+def analysis_results(ctx, result=None, failure=None, expect=None, expect_failure=None):
+  """issue analysis-time success or failure of a test of given ctx based on results."""
+  (is_success, msg) = check_results(result, failure, expect, expect_failure)
+  this_test = success_target if is_success else failure_target
+  return this_test(ctx, msg)
+
+
+
+### Simple tests
+
+def _rule_test_impl(ctx):
+  """check that a rule generates the desired outputs and providers"""
+  rule_ = ctx.attr.rule
+  rule_name = str(rule_.label)
+  if hasattr(ctx, "generates"):
+    prefix = ctx.label.package + "/"
+    generates = ctx.attr.generates
+    generated = [strip_prefix(prefix, f.short_path) for f in rule.files]
+    if not generates == generated:
+      fail("rule %s generates %s not %s"
+        % (rule_name, repr(generated), repr(generates)))
+  if hasattr(ctx, "provides"):
+    # TODO(bazel-team): implement this!
+    fail("provides not implemented yet!")
+  return success_target(ctx, "success")
+
+
+rule_test = rule(
+    implementation=_rule_test_impl,
+    attrs={
+        "rule": attr.label(mandatory=True),
+        "generates": attr.string_list(),
+        "provides": attr.string_dict()},
+    test=True, executable=True)
+
+
+def _file_test_impl(ctx):
+  """check that a file has a given content"""
+  exe = ctx.outputs.executable
+  file = ctx.file.file
+  content = ctx.attr.content
+  regexp = ctx.attr.regexp
+  matches = ctx.attr.matches
+  if (content == "") == (regexp == ""):
+    fail("Must specify one and only one of content or regexp")
+  if content != "" and matches != -1:
+    fail("matches only makes sense with regexp")
+  if content != "":
+    dat = ctx.new_file(ctx.data_configuration.genfiles_dir, exe, ".dat")
+    ctx.file_action(
+      output=dat,
+      content=content)
+    ctx.file_action(
+      output = exe,
+      content = "diff -u %s %s" % (dat.short_path, file.short_path),
+      executable = True)
+    return struct(runfiles=ctx.runfiles([exe, dat, file]))
+  if matches != -1:
+    script = "[ %s == $(grep -c %s %s) ]" % (matches, repr(regexp), file.path)
+  else:
+    script = "grep %s %s" % (repr(regexp), file.path)
+  ctx.file_action(
+    output = exe,
+    content = script,
+    executable = True)
+  return struct(runfiles=ctx.runfiles([exe, file]))
+
+file_test = rule(
+    implementation=_file_test_impl,
+    attrs={
+      "file": attr.label(mandatory=True, allow_files=True, single_file=True),
+      "content": attr.string(default=""),
+      "regexp": attr.string(default=""),
+      "matches": attr.int(default=-1)},
+    test=True, executable=True)
+