Skip to content

Commit e6c0bbf

Browse files
l46kokcopybara-github
authored andcommitted
Introduce CEL Policy Conformance Test Runner for Java
PiperOrigin-RevId: 907725999
1 parent 21c3318 commit e6c0bbf

6 files changed

Lines changed: 315 additions & 0 deletions

File tree

policy/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package(
55
default_visibility = ["//visibility:public"],
66
)
77

8+
exports_files(["cel_policy_conformance_test.bzl"])
9+
810
java_library(
911
name = "policy",
1012
exports = ["//policy/src/main/java/dev/cel/policy"],
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Macro to run CEL policy conformance tests."""
16+
17+
load("@rules_java//java:defs.bzl", "java_test")
18+
19+
def cel_policy_conformance_test_java(name, testdata_dir = "testdata", skip_tests = []):
20+
"""Macro to run CEL policy conformance tests for Java.
21+
22+
Args:
23+
name: The name of the test target.
24+
testdata_dir: The directory containing test cases.
25+
skip_tests: List of test case names (directory names) to skip.
26+
"""
27+
28+
# Find all policy.yaml files to determine test directories
29+
policy_files = native.glob([testdata_dir + "/*/policy.yaml"])
30+
31+
# Extract directory names as test case names
32+
# e.g., "testdata/nested_rule/policy.yaml" -> "nested_rule"
33+
test_cases = []
34+
for f in policy_files:
35+
parts = f.split("/")
36+
if len(parts) >= 2:
37+
test_cases.append(parts[-2])
38+
39+
java_test(
40+
name = name,
41+
jvm_flags = [
42+
"-Ddev.cel.policy.conformance.tests=" + ",".join(test_cases),
43+
"-Ddev.cel.policy.conformance.testdata_dir=" + native.package_name() + "/" + testdata_dir,
44+
"-Ddev.cel.policy.conformance.skip_tests=" + ",".join(skip_tests),
45+
],
46+
data = native.glob([testdata_dir + "/**"]),
47+
size = "small",
48+
test_class = "dev.cel.policy.conformance.PolicyConformanceTests",
49+
runtime_deps = ["//policy/src/test/java/dev/cel/policy/conformance:run"],
50+
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
load("@rules_java//java:defs.bzl", "java_library")
2+
3+
package(
4+
default_applicable_licenses = ["//:license"],
5+
default_testonly = True,
6+
default_visibility = ["//visibility:public"],
7+
)
8+
9+
java_library(
10+
name = "run",
11+
srcs = glob(["*.java"]),
12+
deps = [
13+
"//bundle:cel",
14+
"//common:compiler_common",
15+
"//common/types",
16+
"//runtime:function_binding",
17+
"//testing/testrunner:cel_expression_source",
18+
"//testing/testrunner:cel_test_context",
19+
"//testing/testrunner:cel_test_suite",
20+
"//testing/testrunner:cel_test_suite_text_proto_parser",
21+
"//testing/testrunner:cel_test_suite_yaml_parser",
22+
"//testing/testrunner:test_runner_library",
23+
"@maven//:com_google_guava_guava",
24+
"@maven//:junit_junit",
25+
],
26+
)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.policy.conformance;
16+
17+
import dev.cel.bundle.Cel;
18+
import dev.cel.bundle.CelFactory;
19+
import dev.cel.common.CelFunctionDecl;
20+
import dev.cel.common.CelOverloadDecl;
21+
import dev.cel.common.types.SimpleType;
22+
import dev.cel.runtime.CelFunctionBinding;
23+
import dev.cel.testing.testrunner.CelExpressionSource;
24+
import dev.cel.testing.testrunner.CelTestContext;
25+
import dev.cel.testing.testrunner.CelTestSuite.CelTestSection.CelTestCase;
26+
import dev.cel.testing.testrunner.TestRunnerLibrary;
27+
import java.io.File;
28+
import org.junit.runners.model.Statement;
29+
30+
/** Statement representing a single CEL policy conformance test case. */
31+
public final class PolicyConformanceTest extends Statement {
32+
33+
private final String name;
34+
private final CelTestCase testCase;
35+
private final String dirPath;
36+
37+
public PolicyConformanceTest(String name, CelTestCase testCase, String dirPath) {
38+
this.name = name;
39+
this.testCase = testCase;
40+
this.dirPath = dirPath;
41+
}
42+
43+
public String getName() {
44+
return name;
45+
}
46+
47+
@Override
48+
public void evaluate() throws Throwable {
49+
String policyFile = dirPath + "/policy.yaml";
50+
51+
Cel cel =
52+
CelFactory.standardCelBuilder()
53+
.addFunctionDeclarations(
54+
CelFunctionDecl.newFunctionDeclaration(
55+
"locationCode",
56+
CelOverloadDecl.newGlobalOverload(
57+
"location_code_string", SimpleType.STRING, SimpleType.STRING)))
58+
.addFunctionBindings(
59+
CelFunctionBinding.from(
60+
"location_code_string",
61+
String.class,
62+
(String arg) -> {
63+
switch (arg) {
64+
case "10.0.0.1":
65+
return "us";
66+
case "10.0.0.2":
67+
return "de";
68+
default:
69+
return "ir";
70+
}
71+
}))
72+
.build();
73+
74+
CelTestContext.Builder contextBuilder =
75+
CelTestContext.newBuilder()
76+
.setCelExpression(CelExpressionSource.fromSource(policyFile))
77+
.setCel(cel);
78+
79+
String yamlConfigPath = dirPath + "/config.yaml";
80+
String textprotoConfigPath = dirPath + "/config.textproto";
81+
82+
if (new File(yamlConfigPath).exists()) {
83+
contextBuilder.setConfigFile(yamlConfigPath);
84+
} else if (new File(textprotoConfigPath).exists()) {
85+
contextBuilder.setConfigFile(textprotoConfigPath);
86+
}
87+
88+
TestRunnerLibrary.runTest(testCase, contextBuilder.build());
89+
}
90+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.policy.conformance;
16+
17+
import static java.nio.charset.StandardCharsets.UTF_8;
18+
19+
import com.google.common.base.Splitter;
20+
import com.google.common.base.Strings;
21+
import com.google.common.collect.ImmutableList;
22+
import com.google.common.io.Files;
23+
import dev.cel.testing.testrunner.CelTestSuite;
24+
import dev.cel.testing.testrunner.CelTestSuite.CelTestSection;
25+
import dev.cel.testing.testrunner.CelTestSuite.CelTestSection.CelTestCase;
26+
import dev.cel.testing.testrunner.CelTestSuiteTextProtoParser;
27+
import dev.cel.testing.testrunner.CelTestSuiteYamlParser;
28+
import java.io.File;
29+
import java.util.List;
30+
import org.junit.runner.Description;
31+
import org.junit.runner.notification.RunNotifier;
32+
import org.junit.runners.ParentRunner;
33+
import org.junit.runners.model.InitializationError;
34+
35+
/** Custom JUnit runner for CEL policy conformance tests. */
36+
public final class PolicyConformanceTestRunner extends ParentRunner<PolicyConformanceTest> {
37+
38+
private static final Splitter SPLITTER = Splitter.on(",").omitEmptyStrings();
39+
private static final String TESTS_YAML_FILE_NAME = "tests.yaml";
40+
private static final String TESTS_TEXTPROTO_FILE_NAME = "tests.textproto";
41+
42+
private final ImmutableList<PolicyConformanceTest> tests;
43+
44+
public PolicyConformanceTestRunner(Class<?> clazz) throws InitializationError {
45+
super(clazz);
46+
this.tests = loadTests();
47+
}
48+
49+
private ImmutableList<PolicyConformanceTest> loadTests() {
50+
ImmutableList.Builder<PolicyConformanceTest> testsBuilder = ImmutableList.builder();
51+
String testDirsProp = System.getProperty("dev.cel.policy.conformance.tests");
52+
String testdataDir = System.getProperty("dev.cel.policy.conformance.testdata_dir", "testdata");
53+
String skipTestsProp = System.getProperty("dev.cel.policy.conformance.skip_tests");
54+
ImmutableList<String> testsToSkip =
55+
Strings.isNullOrEmpty(skipTestsProp)
56+
? ImmutableList.of()
57+
: ImmutableList.copyOf(SPLITTER.splitToList(skipTestsProp));
58+
59+
if (Strings.isNullOrEmpty(testDirsProp)) {
60+
return ImmutableList.of();
61+
}
62+
63+
ImmutableList<String> testDirs = ImmutableList.copyOf(SPLITTER.splitToList(testDirsProp));
64+
65+
for (String dir : testDirs) {
66+
String fullDirPath = testdataDir + "/" + dir;
67+
try {
68+
CelTestSuite testSuite = readTestSuite(fullDirPath);
69+
for (CelTestSection section : testSuite.sections()) {
70+
for (CelTestCase testCase : section.tests()) {
71+
String name = String.format("%s/%s/%s", dir, section.name(), testCase.name());
72+
if (!shouldSkipTest(name, testsToSkip)) {
73+
testsBuilder.add(new PolicyConformanceTest(name, testCase, fullDirPath));
74+
}
75+
}
76+
}
77+
} catch (Exception e) {
78+
throw new RuntimeException("Failed to load test suite in " + fullDirPath, e);
79+
}
80+
}
81+
return testsBuilder.build();
82+
}
83+
84+
private static boolean shouldSkipTest(String name, List<String> testsToSkip) {
85+
for (String testToSkip : testsToSkip) {
86+
if (name.startsWith(testToSkip)) {
87+
String consumedName = name.substring(testToSkip.length());
88+
if (consumedName.isEmpty() || consumedName.startsWith("/")) {
89+
return true;
90+
}
91+
}
92+
}
93+
return false;
94+
}
95+
96+
private static CelTestSuite readTestSuite(String dirPath) throws Exception {
97+
File dir = new File(dirPath);
98+
File yamlFile = new File(dir, TESTS_YAML_FILE_NAME);
99+
if (yamlFile.exists()) {
100+
return CelTestSuiteYamlParser.newInstance().parse(Files.asCharSource(yamlFile, UTF_8).read());
101+
}
102+
File textprotoFile = new File(dir, TESTS_TEXTPROTO_FILE_NAME);
103+
if (textprotoFile.exists()) {
104+
return CelTestSuiteTextProtoParser.newInstance()
105+
.parse(Files.asCharSource(textprotoFile, UTF_8).read());
106+
}
107+
throw new IllegalArgumentException(
108+
String.format(
109+
"No %s or %s found in %s", TESTS_YAML_FILE_NAME, TESTS_TEXTPROTO_FILE_NAME, dirPath));
110+
}
111+
112+
@Override
113+
protected ImmutableList<PolicyConformanceTest> getChildren() {
114+
return tests;
115+
}
116+
117+
@Override
118+
protected Description describeChild(PolicyConformanceTest child) {
119+
return Description.createTestDescription(getTestClass().getJavaClass(), child.getName());
120+
}
121+
122+
@Override
123+
protected void runChild(PolicyConformanceTest child, RunNotifier notifier) {
124+
runLeaf(child, describeChild(child), notifier);
125+
}
126+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.policy.conformance;
16+
17+
import org.junit.runner.RunWith;
18+
19+
/** Main test class for CEL policy conformance tests. */
20+
@RunWith(PolicyConformanceTestRunner.class)
21+
public class PolicyConformanceTests {}

0 commit comments

Comments
 (0)