Skip to content

Commit c305a9a

Browse files
committed
Agentic policy compiler
1 parent efba831 commit c305a9a

27 files changed

+1477
-167
lines changed

.bazelversion

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
8.5.0

policy/src/main/java/dev/cel/policy/CelPolicy.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Arrays;
2828
import java.util.Collection;
2929
import java.util.Collections;
30+
import java.util.HashMap;
3031
import java.util.List;
3132
import java.util.Map;
3233
import java.util.Optional;
@@ -77,8 +78,7 @@ public abstract static class Builder {
7778

7879
public abstract Builder setPolicySource(CelPolicySource policySource);
7980

80-
// This should stay package-private to encourage add/set methods to be used instead.
81-
abstract ImmutableMap.Builder<String, Object> metadataBuilder();
81+
private final HashMap<String, Object> metadata = new HashMap<>();
8282

8383
public abstract Builder setMetadata(ImmutableMap<String, Object> value);
8484

@@ -90,6 +90,10 @@ public List<Import> imports() {
9090
return Collections.unmodifiableList(importList);
9191
}
9292

93+
public Map<String, Object> metadata() {
94+
return Collections.unmodifiableMap(metadata);
95+
}
96+
9397
@CanIgnoreReturnValue
9498
public Builder addImport(Import value) {
9599
importList.add(value);
@@ -104,13 +108,13 @@ public Builder addImports(Collection<Import> values) {
104108

105109
@CanIgnoreReturnValue
106110
public Builder putMetadata(String key, Object value) {
107-
metadataBuilder().put(key, value);
111+
metadata.put(key, value);
108112
return this;
109113
}
110114

111115
@CanIgnoreReturnValue
112116
public Builder putMetadata(Map<String, Object> map) {
113-
metadataBuilder().putAll(map);
117+
metadata.putAll(map);
114118
return this;
115119
}
116120

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
load("@rules_java//java:defs.bzl", "java_library")
2+
3+
package(
4+
default_applicable_licenses = ["//:license"],
5+
default_visibility = [
6+
"//policy/testing:__pkg__",
7+
],
8+
)
9+
10+
java_library(
11+
name = "policy_test_suite_helper",
12+
testonly = True,
13+
srcs = [
14+
"PolicyTestSuiteHelper.java",
15+
],
16+
deps = [
17+
"//bundle:cel",
18+
"//common:cel_ast",
19+
"//common:compiler_common",
20+
"//common/formats:value_string",
21+
"//policy",
22+
"//policy:parser",
23+
"//policy:parser_builder",
24+
"//policy:policy_parser_context",
25+
"//runtime:evaluation_exception",
26+
"@maven//:com_google_guava_guava",
27+
"@maven//:org_yaml_snakeyaml",
28+
],
29+
)
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
// Copyright 2024 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.testing;
16+
17+
import static com.google.common.base.Strings.isNullOrEmpty;
18+
import static java.nio.charset.StandardCharsets.UTF_8;
19+
20+
import com.google.common.annotations.VisibleForTesting;
21+
import com.google.common.base.Ascii;
22+
import com.google.common.collect.ImmutableMap;
23+
import com.google.common.io.Resources;
24+
import dev.cel.bundle.Cel;
25+
import dev.cel.common.CelAbstractSyntaxTree;
26+
import dev.cel.common.CelValidationException;
27+
import dev.cel.runtime.CelEvaluationException;
28+
import java.io.IOException;
29+
import java.net.URL;
30+
import java.util.List;
31+
import java.util.Map;
32+
import org.yaml.snakeyaml.LoaderOptions;
33+
import org.yaml.snakeyaml.Yaml;
34+
import org.yaml.snakeyaml.constructor.Constructor;
35+
36+
/**
37+
* Helper to assist with policy testing.
38+
*
39+
**/
40+
public final class PolicyTestSuiteHelper {
41+
42+
/**
43+
* TODO
44+
*/
45+
public static PolicyTestSuite readTestSuite(String path) throws IOException {
46+
Yaml yaml = new Yaml(new Constructor(PolicyTestSuite.class, new LoaderOptions()));
47+
String testContent = readFile(path);
48+
49+
return yaml.load(testContent);
50+
}
51+
52+
/**
53+
* TODO
54+
* @param yamlPath
55+
* @return
56+
* @throws IOException
57+
*/
58+
public static String readFromYaml(String yamlPath) throws IOException {
59+
return readFile(yamlPath);
60+
}
61+
62+
/**
63+
* TestSuite describes a set of tests divided by section.
64+
*
65+
* <p>Visibility must be public for YAML deserialization to work. This is effectively
66+
* package-private since the outer class is.
67+
*/
68+
@VisibleForTesting
69+
public static final class PolicyTestSuite {
70+
private String description;
71+
private List<PolicyTestSection> section;
72+
73+
public void setDescription(String description) {
74+
this.description = description;
75+
}
76+
77+
public void setSection(List<PolicyTestSection> section) {
78+
this.section = section;
79+
}
80+
81+
public String getDescription() {
82+
return description;
83+
}
84+
85+
public List<PolicyTestSection> getSection() {
86+
return section;
87+
}
88+
89+
@VisibleForTesting
90+
public static final class PolicyTestSection {
91+
private String name;
92+
private List<PolicyTestCase> tests;
93+
94+
public void setName(String name) {
95+
this.name = name;
96+
}
97+
98+
public void setTests(List<PolicyTestCase> tests) {
99+
this.tests = tests;
100+
}
101+
102+
public String getName() {
103+
return name;
104+
}
105+
106+
public List<PolicyTestCase> getTests() {
107+
return tests;
108+
}
109+
110+
@VisibleForTesting
111+
public static final class PolicyTestCase {
112+
private String name;
113+
private Map<String, PolicyTestInput> input;
114+
private String output;
115+
116+
public void setName(String name) {
117+
this.name = name;
118+
}
119+
120+
public void setInput(Map<String, PolicyTestInput> input) {
121+
this.input = input;
122+
}
123+
124+
public void setOutput(String output) {
125+
this.output = output;
126+
}
127+
128+
public String getName() {
129+
return name;
130+
}
131+
132+
public Map<String, PolicyTestInput> getInput() {
133+
return input;
134+
}
135+
136+
public String getOutput() {
137+
return output;
138+
}
139+
140+
@VisibleForTesting
141+
public static final class PolicyTestInput {
142+
private Object value;
143+
private String expr;
144+
145+
public Object getValue() {
146+
return value;
147+
}
148+
149+
public void setValue(Object value) {
150+
this.value = value;
151+
}
152+
153+
public String getExpr() {
154+
return expr;
155+
}
156+
157+
public void setExpr(String expr) {
158+
this.expr = expr;
159+
}
160+
}
161+
162+
public ImmutableMap<String, Object> toInputMap(Cel cel)
163+
throws CelValidationException, CelEvaluationException {
164+
ImmutableMap.Builder<String, Object> inputBuilder = ImmutableMap.builderWithExpectedSize(
165+
input.size());
166+
for (Map.Entry<String, PolicyTestInput> entry : input.entrySet()) {
167+
String exprInput = entry.getValue().getExpr();
168+
if (isNullOrEmpty(exprInput)) {
169+
inputBuilder.put(entry.getKey(), entry.getValue().getValue());
170+
} else {
171+
CelAbstractSyntaxTree exprInputAst = cel.compile(exprInput).getAst();
172+
inputBuilder.put(entry.getKey(), cel.createProgram(exprInputAst).eval());
173+
}
174+
}
175+
176+
return inputBuilder.buildOrThrow();
177+
}
178+
}
179+
}
180+
}
181+
182+
183+
private static URL getResource(String path) {
184+
return Resources.getResource(Ascii.toLowerCase(path));
185+
}
186+
187+
private static String readFile(String path) throws IOException {
188+
return Resources.toString(getResource(path), UTF_8);
189+
}
190+
191+
private PolicyTestSuiteHelper() {}
192+
}

policy/src/test/java/dev/cel/policy/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ java_library(
3333
"//policy:policy_parser_context",
3434
"//policy:source",
3535
"//policy:validation_exception",
36+
"//policy/testing:policy_test_suite_helper",
3637
"//runtime",
3738
"//runtime:function_binding",
3839
"//runtime:late_function_binding",

policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@
1414

1515
package dev.cel.policy;
1616

17-
import static com.google.common.base.Strings.isNullOrEmpty;
1817
import static com.google.common.truth.Truth.assertThat;
19-
import static dev.cel.policy.PolicyTestHelper.readFromYaml;
18+
import static dev.cel.policy.testing.PolicyTestSuiteHelper.readFromYaml;
2019
import static org.junit.Assert.assertThrows;
2120

2221
import com.google.common.collect.ImmutableList;
@@ -38,17 +37,15 @@
3837
import dev.cel.parser.CelStandardMacro;
3938
import dev.cel.parser.CelUnparserFactory;
4039
import dev.cel.policy.PolicyTestHelper.K8sTagHandler;
41-
import dev.cel.policy.PolicyTestHelper.PolicyTestSuite;
42-
import dev.cel.policy.PolicyTestHelper.PolicyTestSuite.PolicyTestSection;
43-
import dev.cel.policy.PolicyTestHelper.PolicyTestSuite.PolicyTestSection.PolicyTestCase;
44-
import dev.cel.policy.PolicyTestHelper.PolicyTestSuite.PolicyTestSection.PolicyTestCase.PolicyTestInput;
4540
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
41+
import dev.cel.policy.testing.PolicyTestSuiteHelper.PolicyTestSuite;
42+
import dev.cel.policy.testing.PolicyTestSuiteHelper.PolicyTestSuite.PolicyTestSection;
43+
import dev.cel.policy.testing.PolicyTestSuiteHelper.PolicyTestSuite.PolicyTestSection.PolicyTestCase;
4644
import dev.cel.runtime.CelFunctionBinding;
4745
import dev.cel.runtime.CelLateFunctionBindings;
4846
import dev.cel.testing.testdata.SingleFileProto.SingleFile;
4947
import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum;
5048
import java.io.IOException;
51-
import java.util.Map;
5249
import java.util.Optional;
5350
import org.junit.Test;
5451
import org.junit.runner.RunWith;
@@ -215,17 +212,8 @@ public void evaluateYamlPolicy_withCanonicalTestData(
215212
// Compile then evaluate the policy
216213
CelAbstractSyntaxTree compiledPolicyAst =
217214
CelPolicyCompilerFactory.newPolicyCompiler(cel).build().compile(policy);
218-
ImmutableMap.Builder<String, Object> inputBuilder = ImmutableMap.builder();
219-
for (Map.Entry<String, PolicyTestInput> entry : testData.testCase.getInput().entrySet()) {
220-
String exprInput = entry.getValue().getExpr();
221-
if (isNullOrEmpty(exprInput)) {
222-
inputBuilder.put(entry.getKey(), entry.getValue().getValue());
223-
} else {
224-
CelAbstractSyntaxTree exprInputAst = cel.compile(exprInput).getAst();
225-
inputBuilder.put(entry.getKey(), cel.createProgram(exprInputAst).eval());
226-
}
227-
}
228-
Object evalResult = cel.createProgram(compiledPolicyAst).eval(inputBuilder.buildOrThrow());
215+
ImmutableMap<String, Object> inputMap = testData.testCase.toInputMap(cel);
216+
Object evalResult = cel.createProgram(compiledPolicyAst).eval(inputMap);
229217

230218
// Assert
231219
// Note that policies may either produce an optional or a non-optional result,

0 commit comments

Comments
 (0)