aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar David Chen <dzc@google.com>2015-10-22 09:35:11 +0000
committerGravatar Kristina Chodorow <kchodorow@google.com>2015-10-22 15:17:19 +0000
commit854b72dddb80a4c8da6bf0ac9dbd683951875541 (patch)
treee55cce6f32ed203070f60e2aa6ce8fbe70edc936
parent16f10837a224df9d792cba563dbee138a0789cb7 (diff)
[jsonnet] Add jsonnet_to_json_test rule for testing Jsonnet code.
RELNOTES: [jsonnet] Add jsonnet_to_json_test rule for testing Jsonnet code. -- MOS_MIGRATED_REVID=106040951
-rw-r--r--WORKSPACE3
-rw-r--r--examples/jsonnet/BUILD28
-rw-r--r--examples/jsonnet/intersection_golden.json48
-rw-r--r--examples/jsonnet/invalid.jsonnet15
-rw-r--r--examples/jsonnet/invalid.out2
-rw-r--r--examples/jsonnet/wordcount_golden.json53
-rw-r--r--tools/build_defs/jsonnet/README.md212
-rw-r--r--tools/build_defs/jsonnet/jsonnet.BUILD80
-rw-r--r--tools/build_defs/jsonnet/jsonnet.WORKSPACE3
-rw-r--r--tools/build_defs/jsonnet/jsonnet.bzl102
10 files changed, 455 insertions, 91 deletions
diff --git a/WORKSPACE b/WORKSPACE
index 8e226bdad0..a72072df1a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -60,11 +60,10 @@ new_http_archive(
build_file = "tools/build_defs/d/dmd.BUILD",
)
-new_git_repository(
+git_repository(
name = "jsonnet",
remote = "https://github.com/google/jsonnet.git",
tag = "v0.8.1",
- build_file = "tools/build_defs/jsonnet/jsonnet.BUILD",
)
new_http_archive(
diff --git a/examples/jsonnet/BUILD b/examples/jsonnet/BUILD
index 3c829df876..735057ed17 100644
--- a/examples/jsonnet/BUILD
+++ b/examples/jsonnet/BUILD
@@ -1,6 +1,11 @@
package(default_visibility = ["//visibility:public"])
-load("/tools/build_defs/jsonnet/jsonnet", "jsonnet_library", "jsonnet_to_json")
+load(
+ "/tools/build_defs/jsonnet/jsonnet",
+ "jsonnet_library",
+ "jsonnet_to_json",
+ "jsonnet_to_json_test",
+)
jsonnet_library(
name = "workflow",
@@ -14,6 +19,13 @@ jsonnet_to_json(
deps = [":workflow"],
)
+jsonnet_to_json_test(
+ name = "wordcount_test",
+ src = "wordcount.jsonnet",
+ golden = "wordcount_golden.json",
+ deps = [":workflow"],
+)
+
jsonnet_to_json(
name = "intersection",
src = "intersection.jsonnet",
@@ -21,6 +33,13 @@ jsonnet_to_json(
deps = [":workflow"],
)
+jsonnet_to_json_test(
+ name = "intersection_test",
+ src = "intersection.jsonnet",
+ golden = "intersection_golden.json",
+ deps = [":workflow"],
+)
+
jsonnet_library(
name = "shell-workflows-lib",
srcs = [
@@ -39,3 +58,10 @@ jsonnet_to_json(
],
deps = [":shell-workflows-lib"],
)
+
+jsonnet_to_json_test(
+ name = "invalid_test",
+ src = "invalid.jsonnet",
+ error = 1,
+ golden = "invalid.out",
+)
diff --git a/examples/jsonnet/intersection_golden.json b/examples/jsonnet/intersection_golden.json
new file mode 100644
index 0000000000..6e8fa63864
--- /dev/null
+++ b/examples/jsonnet/intersection_golden.json
@@ -0,0 +1,48 @@
+{
+ "intersection": {
+ "jobs": {
+ "intersect": {
+ "command": "comm -12 /tmp/list1_sorted /tmp/list2_sorted > /tmp/intersection",
+ "deps": [
+ ":sort_file1",
+ ":sort_file2"
+ ],
+ "inputs": [
+ "/tmp/list1_sorted",
+ "/tmp/list2_sorted"
+ ],
+ "outputs": [
+ "/tmp/intersection"
+ ],
+ "type": "sh",
+ "vars": { }
+ },
+ "sort_file1": {
+ "command": "sort /tmp/list1 > /tmp/list1_sorted",
+ "deps": [ ],
+ "inputs": [
+ "/tmp/list1"
+ ],
+ "outputs": [
+ "/tmp/list1_sorted"
+ ],
+ "type": "sh",
+ "vars": { }
+ },
+ "sort_file2": {
+ "command": "sort /tmp/list2 > /tmp/list2_sorted",
+ "deps": [ ],
+ "inputs": [
+ "/tmp/list2"
+ ],
+ "outputs": [
+ "/tmp/list2_sorted"
+ ],
+ "type": "sh",
+ "vars": { }
+ }
+ },
+ "retries": 5,
+ "schedule": { }
+ }
+}
diff --git a/examples/jsonnet/invalid.jsonnet b/examples/jsonnet/invalid.jsonnet
new file mode 100644
index 0000000000..e911037832
--- /dev/null
+++ b/examples/jsonnet/invalid.jsonnet
@@ -0,0 +1,15 @@
+// Copyright 2015 The Bazel Authors. 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.
+
+error "Foo."
diff --git a/examples/jsonnet/invalid.out b/examples/jsonnet/invalid.out
new file mode 100644
index 0000000000..6c220f5170
--- /dev/null
+++ b/examples/jsonnet/invalid.out
@@ -0,0 +1,2 @@
+RUNTIME ERROR: Foo.
+ examples/jsonnet/invalid.jsonnet:15:1-12
diff --git a/examples/jsonnet/wordcount_golden.json b/examples/jsonnet/wordcount_golden.json
new file mode 100644
index 0000000000..25d99cf3ab
--- /dev/null
+++ b/examples/jsonnet/wordcount_golden.json
@@ -0,0 +1,53 @@
+{
+ "wordcount": {
+ "jobs": {
+ "count": {
+ "command": "uniq -c /tmp/sorted_tokens > /tmp/counts",
+ "deps": [
+ ":sort"
+ ],
+ "inputs": [
+ "/tmp/sorted_tokens"
+ ],
+ "outputs": [
+ "/tmp/counts"
+ ],
+ "type": "sh",
+ "vars": { }
+ },
+ "sort": {
+ "command": "sort /tmp/tokens > /tmp/sorted_tokens",
+ "deps": [
+ ":tokenize"
+ ],
+ "inputs": [
+ "/tmp/tokens"
+ ],
+ "outputs": [
+ "/tmp/sorted_tokens"
+ ],
+ "type": "sh",
+ "vars": { }
+ },
+ "tokenize": {
+ "command": "tr ' ' '\n' < /tmp/passage_test > /tmp/tokens",
+ "deps": [ ],
+ "inputs": [
+ "/tmp/passage_test"
+ ],
+ "outputs": [
+ "/tmp/tokens"
+ ],
+ "type": "sh",
+ "vars": { }
+ }
+ },
+ "retries": 12,
+ "schedule": {
+ "repeat_frequency": 1,
+ "repeat_type": "week",
+ "start_date": "2015-11-15",
+ "start_time": "17:30"
+ }
+ }
+}
diff --git a/tools/build_defs/jsonnet/README.md b/tools/build_defs/jsonnet/README.md
index 32865a87ec..7d28f15133 100644
--- a/tools/build_defs/jsonnet/README.md
+++ b/tools/build_defs/jsonnet/README.md
@@ -4,6 +4,7 @@
* [`jsonnet_library`](#jsonnet_library)
* [`jsonnet_to_json`](#jsonnet_to_json)
+* [`jsonnet_to_json_test`](#jsonnet_to_json_test)
## Overview
@@ -79,9 +80,9 @@ Suppose you have the following directory structure:
[workspace]/
WORKSPACE
configs/
- BUILD
- backend.jsonnet
- frontend.jsonnet
+ BUILD
+ backend.jsonnet
+ frontend.jsonnet
```
You can use the `jsonnet_library` rule to build a collection of `.jsonnet`
@@ -327,3 +328,208 @@ jsonnet_to_json(
```
[multiple-output-files]: http://google.github.io/jsonnet/doc/commandline.html
+
+<a name="#jsonnet_to_json_test"></a>
+## jsonnet_to_json_test
+
+```python
+jsonnet_to_json_test(name, src, deps, imports, golden, error=0, regex=False)
+```
+
+<table>
+ <thead>
+ <tr>
+ <th>Attribute</th>
+ <th>Description</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><code>name</code></td>
+ <td>
+ <code>Name, required</code>
+ <p>A unique name for this rule.</p>
+ <p>
+ This name will be used as the name of the JSON file generated by this
+ rule.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>src</code></td>
+ <td>
+ <code>Label, required</code>
+ <p>
+ The <code>.jsonnet</code> file to convert to JSON.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>deps</code></td>
+ <td>
+ <code>List of labels, optional</code>
+ <p>
+ List of targets that are required by the <code>src</code> Jsonnet
+ file.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>imports</code></td>
+ <td>
+ <code>List of strings, optional</code>
+ <p>
+ List of import <code>-J</code> flags to be passed to the
+ <code>jsonnet</code> compiler.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>vars</code></td>
+ <td>
+ <code>String dict, optional</code>
+ <p>
+ Map of variables to pass to jsonnet via <code>--var key=value</code>.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>code_vars</code></td>
+ <td>
+ <code>String dict, optional</code>
+ <p>
+ Map of code variables to pass to jsonnet via
+ <code>--code-var key=value</code>.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>golden</code></td>
+ <td>
+ <code>Label, optional</code>
+ <p>
+ The expected (combined stdout and stderr) output to compare to the
+ output of running <code>jsonnet</code> on <code>src</code>.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>error</code></td>
+ <td>
+ <code>Integer, optional, default is 0</code>
+ <p>
+ The expected error code from running <code>jsonnet</code> on
+ <code>src</code>.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>regex</code></td>
+ <td>
+ <code>bool, optional, default is False</code>
+ <p>
+ Set to 1 if <code>golden</code> contains a regex used to match
+ the output of running <code>jsonnet</code> on <code>src</code>.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+### Example
+
+Suppose you have the following directory structure:
+
+```
+[workspace]/
+ WORKSPACE
+ config/
+ BUILD
+ base_config.jsonnet
+ test_config.jsonnet
+ test_config.json
+```
+
+Suppose that `base_config.jsonnet` is a library Jsonnet file, containing the
+base configuration for a service. Suppose that `test_config.jsonnet` is a test
+configuration file that is used to test `base_config.jsonnet`, and
+`test_config.json` is the expected JSON output from compiling
+`test_config.jsonnet`.
+
+The `jsonnet_to_json_test` rule can be used to verify that compiling a Jsonnet
+file produces the expected JSON output. Simply define a `jsonnet_to_json_test`
+target and provide the input test Jsonnet file and the `golden` file containing
+the expected JSON output:
+
+`config/BUILD`:
+
+```python
+load(
+ "/tools/build_defs/jsonnet/jsonnet",
+ "jsonnet_library",
+ "jsonnet_to_json_test",
+)
+
+jsonnet_library(
+ name = "base_config",
+ srcs = ["base_config.jsonnet"],
+)
+
+jsonnet_to_json_test(
+ name = "test_config_test",
+ src = "test_config",
+ deps = [":base_config"],
+ golden = "test_config.json",
+)
+```
+
+To run the test: `bazel test //config:test_config_test`
+
+### Example: Negative tests
+
+Suppose you have the following directory structure:
+
+```
+[workspace]/
+ WORKSPACE
+ config/
+ BUILD
+ base_config.jsonnet
+ invalid_config.jsonnet
+ invalid_config.output
+```
+
+Suppose that `invalid_config.jsonnet` is a Jsonnet file used to verify that
+an invalid config triggers an assertion in `base_config.jsonnet`, and
+`invalid_config.output` is the expected error output.
+
+The `jsonnet_to_json_test` rule can be used to verify that compiling a Jsonnet
+file results in an expected error code and error output. Simply define a
+`jsonnet_to_json_test` target and provide the input test Jsonnet file, the
+expected error code in the `error` attribute, and the `golden` file containing
+the expected error output:
+
+`config/BUILD`:
+
+```python
+load(
+ "/tools/build_defs/jsonnet/jsonnet",
+ "jsonnet_library",
+ "jsonnet_to_json_test",
+)
+
+jsonnet_library(
+ name = "base_config",
+ srcs = ["base_config.jsonnet"],
+)
+
+jsonnet_to_json_test(
+ name = "invalid_config_test",
+ src = "invalid_config",
+ deps = [":base_config"],
+ golden = "invalid_config.output",
+ error = 1,
+)
+```
+
+To run the test: `bazel test //config:invalid_config_test`
diff --git a/tools/build_defs/jsonnet/jsonnet.BUILD b/tools/build_defs/jsonnet/jsonnet.BUILD
deleted file mode 100644
index 4b7dca6ea6..0000000000
--- a/tools/build_defs/jsonnet/jsonnet.BUILD
+++ /dev/null
@@ -1,80 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-filegroup(
- name = "std",
- srcs = ["std.jsonnet"],
-)
-
-genrule(
- name = "gen-std-jsonnet-h",
- srcs = ["std.jsonnet"],
- outs = ["std.jsonnet.h"],
- cmd = "((od -v -Anone -t u1 $< | tr \" \" \"\n\" | grep -v \"^$$\" " +
- "| tr \"\n\" \",\" ) && echo \"0\") > $@; " +
- "echo >> $@",
-)
-
-cc_library(
- name = "jsonnet-common",
- srcs = [
- "lexer.cpp",
- "parser.cpp",
- "static_analysis.cpp",
- "vm.cpp",
- "std.jsonnet.h",
- ],
- hdrs = [
- "lexer.h",
- "parser.h",
- "static_analysis.h",
- "static_error.h",
- "vm.h",
- ],
- linkopts = ["-lm"],
- includes = ["."],
-)
-
-cc_library(
- name = "libjsonnet",
- srcs = ["libjsonnet.cpp"],
- hdrs = ["libjsonnet.h"],
- deps = [":jsonnet-common"],
- includes = ["."],
-)
-
-cc_binary(
- name = "jsonnet",
- srcs = ["jsonnet.cpp"],
- deps = [":libjsonnet"],
- includes = ["."],
-)
-
-cc_binary(
- name = "libjsonnet_test_snippet",
- srcs = ["libjsonnet_test_snippet.c"],
- deps = [":libjsonnet"],
- includes = ["."],
-)
-
-cc_binary(
- name = "libjsonnet_test_file",
- srcs = ["libjsonnet_test_file.c"],
- deps = [":libjsonnet"],
- includes = ["."],
-)
-
-filegroup(
- name = "object_jsonnet",
- srcs = ["test_suite/object.jsonnet"],
-)
-
-sh_test(
- name = "libjsonnet_test",
- srcs = ["libjsonnet_test.sh"],
- data = [
- ":jsonnet",
- ":libjsonnet_test_snippet",
- ":libjsonnet_test_file",
- ":object_jsonnet",
- ],
-)
diff --git a/tools/build_defs/jsonnet/jsonnet.WORKSPACE b/tools/build_defs/jsonnet/jsonnet.WORKSPACE
index 963eeb4680..bce62ccff2 100644
--- a/tools/build_defs/jsonnet/jsonnet.WORKSPACE
+++ b/tools/build_defs/jsonnet/jsonnet.WORKSPACE
@@ -1,6 +1,5 @@
-new_git_repository(
+git_repository(
name = "jsonnet",
remote = "https://github.com/google/jsonnet.git",
tag = "v0.8.1",
- build_file = "tools/build_defs/jsonnet/jsonnet.BUILD",
)
diff --git a/tools/build_defs/jsonnet/jsonnet.bzl b/tools/build_defs/jsonnet/jsonnet.bzl
index 2689eb7c9b..fdc825ad84 100644
--- a/tools/build_defs/jsonnet/jsonnet.bzl
+++ b/tools/build_defs/jsonnet/jsonnet.bzl
@@ -14,7 +14,7 @@
"""Jsonnet rules for Bazel."""
-JSONNET_FILETYPE = FileType([".jsonnet"])
+_JSONNET_FILETYPE = FileType([".jsonnet"])
def _setup_deps(deps):
"""Collects source files and import flags of transitive dependencies.
@@ -104,6 +104,89 @@ def _jsonnet_to_json_impl(ctx):
use_default_shell_env = True,
progress_message = "Compiling Jsonnet to JSON for " + ctx.label.name);
+_EXIT_CODE_COMPARE_COMMAND = """
+EXIT_CODE=$?
+EXPECTED_EXIT_CODE=%d
+if [ $EXIT_CODE -ne $EXPECTED_EXIT_CODE ] ; then
+ echo "FAIL (exit code): %s"
+ echo "Expected: $EXPECTED_EXIT_CODE"
+ echo "Actual: $EXIT_CODE"
+ echo "Output: $OUTPUT"
+ exit 1
+fi
+"""
+
+_DIFF_COMMAND = """
+GOLDEN=$(cat %s)
+if [ "$OUTPUT" != "$GOLDEN" ]; then
+ echo "FAIL (output mismatch): %s"
+ echo "Diff:"
+ diff <(echo $GOLDEN) <(echo $OUTPUT)
+ echo "Expected: $GOLDEN"
+ echo "Actual: $OUTPUT"
+ exit 1
+fi
+"""
+
+_REGEX_DIFF_COMMAND = """
+GOLDEN_REGEX=$(cat %s)
+if [[ ! "$OUTPUT" =~ $GOLDEN_REGEX ]]; then
+ echo "FAIL (regex mismatch): %s"
+ echo "Output: $OUTPUT"
+ exit 1
+fi
+"""
+
+def _jsonnet_to_json_test_impl(ctx):
+ """Implementation of the jsonnet_to_json_test rule."""
+ depinfo = _setup_deps(ctx.attr.deps)
+ toolchain = _jsonnet_toolchain(ctx)
+
+ golden_files = []
+ if ctx.file.golden:
+ golden_files += [ctx.file.golden]
+ if ctx.attr.regex:
+ diff_command = _REGEX_DIFF_COMMAND % (ctx.file.golden.short_path,
+ ctx.label.name)
+ else:
+ diff_command = _DIFF_COMMAND % (ctx.file.golden.short_path,
+ ctx.label.name)
+
+ jsonnet_vars = ctx.attr.vars
+ jsonnet_code_vars = ctx.attr.code_vars
+ jsonnet_command = " ".join(
+ ["OUTPUT=$(%s" % ctx.file._jsonnet.short_path] +
+ ["-J %s/%s" % (ctx.label.package, im) for im in ctx.attr.imports] +
+ ["-J %s" % im for im in depinfo.imports] +
+ toolchain.imports +
+ ["-J ."] +
+ ["--var %s=%s"
+ % (var, jsonnet_vars[var]) for var in jsonnet_vars.keys()] +
+ ["--code-var %s=%s"
+ % (var, jsonnet_code_vars[var]) for var in jsonnet_vars.keys()] +
+ [
+ ctx.file.src.path,
+ "2>&1)",
+ ])
+
+ command = "\n".join([
+ "#!/bin/bash",
+ jsonnet_command,
+ _EXIT_CODE_COMPARE_COMMAND % (ctx.attr.error, ctx.label.name),
+ diff_command])
+
+ ctx.file_action(output = ctx.outputs.executable,
+ content = command,
+ executable = True);
+
+ test_inputs = (
+ [ctx.file.src, ctx.file._jsonnet, ctx.file._std] +
+ golden_files +
+ list(depinfo.transitive_sources))
+
+ return struct(
+ runfiles = ctx.runfiles(files = test_inputs, collect_data = True))
+
_jsonnet_common_attrs = {
"deps": attr.label_list(providers = ["transitive_jsonnet_files"],
allow_files = False),
@@ -117,7 +200,7 @@ _jsonnet_common_attrs = {
}
_jsonnet_library_attrs = {
- "srcs": attr.label_list(allow_files = JSONNET_FILETYPE),
+ "srcs": attr.label_list(allow_files = _JSONNET_FILETYPE),
}
jsonnet_library = rule(
@@ -126,7 +209,7 @@ jsonnet_library = rule(
)
_jsonnet_compile_attrs = {
- "src": attr.label(allow_files = JSONNET_FILETYPE,
+ "src": attr.label(allow_files = _JSONNET_FILETYPE,
single_file = True),
"vars": attr.string_dict(),
"code_vars": attr.string_dict(),
@@ -141,3 +224,16 @@ jsonnet_to_json = rule(
_jsonnet_to_json_impl,
attrs = _jsonnet_to_json_attrs + _jsonnet_common_attrs,
)
+
+_jsonnet_to_json_test_attrs = _jsonnet_compile_attrs + {
+ "golden": attr.label(allow_files = True, single_file = True),
+ "error": attr.int(),
+ "regex": attr.bool(),
+}
+
+jsonnet_to_json_test = rule(
+ _jsonnet_to_json_test_impl,
+ attrs = _jsonnet_to_json_test_attrs + _jsonnet_common_attrs,
+ executable = True,
+ test = True,
+)