diff options
-rw-r--r-- | WORKSPACE | 3 | ||||
-rw-r--r-- | examples/jsonnet/BUILD | 28 | ||||
-rw-r--r-- | examples/jsonnet/intersection_golden.json | 48 | ||||
-rw-r--r-- | examples/jsonnet/invalid.jsonnet | 15 | ||||
-rw-r--r-- | examples/jsonnet/invalid.out | 2 | ||||
-rw-r--r-- | examples/jsonnet/wordcount_golden.json | 53 | ||||
-rw-r--r-- | tools/build_defs/jsonnet/README.md | 212 | ||||
-rw-r--r-- | tools/build_defs/jsonnet/jsonnet.BUILD | 80 | ||||
-rw-r--r-- | tools/build_defs/jsonnet/jsonnet.WORKSPACE | 3 | ||||
-rw-r--r-- | tools/build_defs/jsonnet/jsonnet.bzl | 102 |
10 files changed, 455 insertions, 91 deletions
@@ -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, +) |