blob: f1339bd4ce7f539033a0d68546f2684abed9e68c [file] [log] [blame]
Lukacs Berki14328eb2015-10-21 10:47:26 +00001// Copyright 2006 The Bazel Authors. All Rights Reserved.
Ulf Adams89f012d2015-02-26 13:39:28 +00002//
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// http://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
15package com.google.devtools.build.lib.syntax;
16
Ulf Adams795895a2015-03-06 15:58:35 +000017import static com.google.common.truth.Truth.assertThat;
cushon978cb002018-02-24 14:05:37 -080018import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000019import static org.junit.Assert.fail;
20
brandjon2c98fff2018-02-06 01:32:46 -080021import com.google.common.collect.ImmutableMap;
Ulf Adams89f012d2015-02-26 13:39:28 +000022import com.google.common.collect.Sets;
brandjon2c98fff2018-02-06 01:32:46 -080023import com.google.devtools.build.lib.syntax.Environment.Extension;
Han-Wen Nienhuysceae8c52015-09-22 16:24:45 +000024import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000025import org.junit.Test;
26import org.junit.runner.RunWith;
27import org.junit.runners.JUnit4;
28
Ulf Adams89f012d2015-02-26 13:39:28 +000029/**
30 * Tests of Environment.
31 */
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000032@RunWith(JUnit4.class)
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000033public class EnvironmentTest extends EvaluationTestCase {
34
35 @Override
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000036 public Environment newEnvironment() {
37 return newBuildEnvironment();
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000038 }
Ulf Adams89f012d2015-02-26 13:39:28 +000039
40 // Test the API directly
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000041 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +000042 public void testLookupAndUpdate() throws Exception {
lberkiaea56b32017-05-30 12:35:33 +020043 assertThat(lookup("foo")).isNull();
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000044 update("foo", "bar");
lberkiaea56b32017-05-30 12:35:33 +020045 assertThat(lookup("foo")).isEqualTo("bar");
Ulf Adams89f012d2015-02-26 13:39:28 +000046 }
47
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000048 @Test
Laurent Le Brun1afb0b22016-10-26 12:46:42 +000049 public void testHasVariable() throws Exception {
50 assertThat(getEnvironment().hasVariable("VERSION")).isFalse();
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000051 update("VERSION", 42);
Laurent Le Brun1afb0b22016-10-26 12:46:42 +000052 assertThat(getEnvironment().hasVariable("VERSION")).isTrue();
Ulf Adams89f012d2015-02-26 13:39:28 +000053 }
54
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000055 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +000056 public void testDoubleUpdateSucceeds() throws Exception {
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000057 update("VERSION", 42);
lberkiaea56b32017-05-30 12:35:33 +020058 assertThat(lookup("VERSION")).isEqualTo(42);
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000059 update("VERSION", 43);
lberkiaea56b32017-05-30 12:35:33 +020060 assertThat(lookup("VERSION")).isEqualTo(43);
Ulf Adams89f012d2015-02-26 13:39:28 +000061 }
62
63 // Test assign through interpreter, lookup through API:
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000064 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +000065 public void testAssign() throws Exception {
lberkiaea56b32017-05-30 12:35:33 +020066 assertThat(lookup("foo")).isNull();
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000067 eval("foo = 'bar'");
lberkiaea56b32017-05-30 12:35:33 +020068 assertThat(lookup("foo")).isEqualTo("bar");
Ulf Adams89f012d2015-02-26 13:39:28 +000069 }
70
71 // Test update through API, reference through interpreter:
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000072 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +000073 public void testReference() throws Exception {
Laurent Le Bruna6d8fe42016-11-28 18:14:54 +000074 setFailFast(false);
Ulf Adams89f012d2015-02-26 13:39:28 +000075 try {
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000076 eval("foo");
Ulf Adams89f012d2015-02-26 13:39:28 +000077 fail();
78 } catch (EvalException e) {
Ulf Adams795895a2015-03-06 15:58:35 +000079 assertThat(e).hasMessage("name 'foo' is not defined");
Ulf Adams89f012d2015-02-26 13:39:28 +000080 }
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000081 update("foo", "bar");
lberkiaea56b32017-05-30 12:35:33 +020082 assertThat(eval("foo")).isEqualTo("bar");
Ulf Adams89f012d2015-02-26 13:39:28 +000083 }
84
85 // Test assign and reference through interpreter:
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000086 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +000087 public void testAssignAndReference() throws Exception {
Laurent Le Bruna6d8fe42016-11-28 18:14:54 +000088 setFailFast(false);
Ulf Adams89f012d2015-02-26 13:39:28 +000089 try {
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000090 eval("foo");
Ulf Adams89f012d2015-02-26 13:39:28 +000091 fail();
92 } catch (EvalException e) {
Ulf Adams795895a2015-03-06 15:58:35 +000093 assertThat(e).hasMessage("name 'foo' is not defined");
Ulf Adams89f012d2015-02-26 13:39:28 +000094 }
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000095 eval("foo = 'bar'");
lberkiaea56b32017-05-30 12:35:33 +020096 assertThat(eval("foo")).isEqualTo("bar");
Ulf Adams89f012d2015-02-26 13:39:28 +000097 }
98
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000099 @Test
brandjonb368b392017-10-20 22:02:02 +0200100 public void testBuilderRequiresSemantics() throws Exception {
101 try (Mutability mut = Mutability.create("test")) {
102 IllegalArgumentException expected =
cushon978cb002018-02-24 14:05:37 -0800103 assertThrows(IllegalArgumentException.class, () -> Environment.builder(mut).build());
brandjonb368b392017-10-20 22:02:02 +0200104 assertThat(expected).hasMessageThat()
105 .contains("must call either setSemantics or useDefaultSemantics");
106 }
107 }
108
109 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000110 public void testGetVariableNames() throws Exception {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000111 Environment outerEnv;
112 Environment innerEnv;
113 try (Mutability mut = Mutability.create("outer")) {
Laurent Le Brun5e991982016-10-14 13:39:45 +0000114 outerEnv =
115 Environment.builder(mut)
brandjon10a6b772017-10-20 20:48:30 +0200116 .useDefaultSemantics()
Laurent Le Brun5e991982016-10-14 13:39:45 +0000117 .setGlobals(Environment.DEFAULT_GLOBALS)
118 .build()
119 .update("foo", "bar")
120 .update("wiz", 3);
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000121 }
122 try (Mutability mut = Mutability.create("inner")) {
123 innerEnv = Environment.builder(mut)
brandjon10a6b772017-10-20 20:48:30 +0200124 .useDefaultSemantics()
125 .setGlobals(outerEnv.getGlobals())
126 .build()
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000127 .update("foo", "bat")
128 .update("quux", 42);
129 }
Ulf Adams89f012d2015-02-26 13:39:28 +0000130
lberkiaea56b32017-05-30 12:35:33 +0200131 assertThat(outerEnv.getVariableNames())
132 .isEqualTo(
133 Sets.newHashSet(
134 "foo",
135 "wiz",
136 "False",
137 "None",
138 "True",
lberkiaea56b32017-05-30 12:35:33 +0200139 "all",
140 "any",
141 "bool",
142 "dict",
143 "dir",
144 "enumerate",
145 "fail",
146 "getattr",
147 "hasattr",
148 "hash",
149 "int",
150 "len",
151 "list",
152 "max",
153 "min",
154 "print",
155 "range",
156 "repr",
157 "reversed",
158 "sorted",
159 "str",
160 "tuple",
161 "zip"));
162 assertThat(innerEnv.getVariableNames())
163 .isEqualTo(
164 Sets.newHashSet(
165 "foo",
166 "wiz",
167 "quux",
168 "False",
169 "None",
170 "True",
lberkiaea56b32017-05-30 12:35:33 +0200171 "all",
172 "any",
173 "bool",
174 "dict",
175 "dir",
176 "enumerate",
177 "fail",
178 "getattr",
179 "hasattr",
180 "hash",
181 "int",
182 "len",
183 "list",
184 "max",
185 "min",
186 "print",
187 "range",
188 "repr",
189 "reversed",
190 "sorted",
191 "str",
192 "tuple",
193 "zip"));
Ulf Adams89f012d2015-02-26 13:39:28 +0000194 }
195
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000196 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000197 public void testToString() throws Exception {
Michajlo Matijkiw8c539ea2017-02-22 23:02:46 +0000198 update("subject", new StringLiteral("Hello, 'world'."));
199 update("from", new StringLiteral("Java"));
Francois-Rene Rideaubd0c7bb2015-09-21 16:54:19 +0000200 assertThat(getEnvironment().toString()).isEqualTo("<Environment[test]>");
Ulf Adams89f012d2015-02-26 13:39:28 +0000201 }
202
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000203 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000204 public void testBindToNullThrowsException() throws Exception {
205 try {
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000206 update("some_name", null);
Ulf Adams89f012d2015-02-26 13:39:28 +0000207 fail();
208 } catch (NullPointerException e) {
Ulf Adams795895a2015-03-06 15:58:35 +0000209 assertThat(e).hasMessage("update(value == null)");
Ulf Adams89f012d2015-02-26 13:39:28 +0000210 }
211 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000212
213 @Test
214 public void testFrozen() throws Exception {
215 Environment env;
216 try (Mutability mutability = Mutability.create("testFrozen")) {
Laurent Le Brun5e991982016-10-14 13:39:45 +0000217 env =
218 Environment.builder(mutability)
brandjon10a6b772017-10-20 20:48:30 +0200219 .useDefaultSemantics()
Laurent Le Brun5e991982016-10-14 13:39:45 +0000220 .setGlobals(Environment.DEFAULT_GLOBALS)
221 .setEventHandler(Environment.FAIL_FAST_HANDLER)
222 .build();
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000223 env.update("x", 1);
lberkiaea56b32017-05-30 12:35:33 +0200224 assertThat(env.lookup("x")).isEqualTo(1);
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000225 env.update("y", 2);
lberkiaea56b32017-05-30 12:35:33 +0200226 assertThat(env.lookup("y")).isEqualTo(2);
227 assertThat(env.lookup("x")).isEqualTo(1);
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000228 env.update("x", 3);
lberkiaea56b32017-05-30 12:35:33 +0200229 assertThat(env.lookup("x")).isEqualTo(3);
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000230 }
231 try {
232 // This update to an existing variable should fail because the environment was frozen.
233 env.update("x", 4);
234 throw new Exception("failed to fail"); // not an AssertionError like fail()
235 } catch (AssertionError e) {
236 assertThat(e).hasMessage("Can't update x to 4 in frozen environment");
237 }
238 try {
239 // This update to a new variable should also fail because the environment was frozen.
240 env.update("newvar", 5);
241 throw new Exception("failed to fail"); // not an AssertionError like fail()
242 } catch (AssertionError e) {
243 assertThat(e).hasMessage("Can't update newvar to 5 in frozen environment");
244 }
245 }
246
247 @Test
248 public void testReadOnly() throws Exception {
249 Environment env = newSkylarkEnvironment()
250 .setup("special_var", 42)
251 .update("global_var", 666);
252
253 // We don't even get a runtime exception trying to modify these,
254 // because we get compile-time exceptions even before we reach runtime!
255 try {
Laurent Le Bruna6d8fe42016-11-28 18:14:54 +0000256 BuildFileAST.eval(env, "special_var = 41");
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000257 throw new AssertionError("failed to fail");
Laurent Le Brund4d7fca2017-02-14 19:12:02 +0000258 } catch (EvalException e) {
lberkiaea56b32017-05-30 12:35:33 +0200259 assertThat(e).hasMessageThat().contains("Variable special_var is read only");
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000260 }
261
262 try {
Laurent Le Bruna6d8fe42016-11-28 18:14:54 +0000263 BuildFileAST.eval(env, "def foo(x): x += global_var; global_var = 36; return x", "foo(1)");
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000264 throw new AssertionError("failed to fail");
265 } catch (EvalExceptionWithStackTrace e) {
lberkiaea56b32017-05-30 12:35:33 +0200266 assertThat(e)
267 .hasMessageThat()
268 .contains(
269 "Variable 'global_var' is referenced before assignment. "
270 + "The variable is defined in the global scope.");
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000271 }
272 }
brandjon2c98fff2018-02-06 01:32:46 -0800273
274 @Test
brandjon73e768e2018-02-13 12:26:52 -0800275 public void testVarOrderDeterminism() throws Exception {
276 Mutability parentMutability = Mutability.create("parent env");
277 Environment parentEnv = Environment.builder(parentMutability)
278 .useDefaultSemantics()
279 .build();
280 parentEnv.update("a", 1);
281 parentEnv.update("c", 2);
282 parentEnv.update("b", 3);
nharmata68cc06b2018-03-01 10:21:57 -0800283 Environment.GlobalFrame parentFrame = parentEnv.getGlobals();
brandjon73e768e2018-02-13 12:26:52 -0800284 parentMutability.freeze();
285 Mutability mutability = Mutability.create("testing");
286 Environment env = Environment.builder(mutability)
287 .useDefaultSemantics()
288 .setGlobals(parentFrame)
289 .build();
290 env.update("x", 4);
291 env.update("z", 5);
292 env.update("y", 6);
293 // The order just has to be deterministic, but for definiteness this test spells out the exact
294 // order returned by the implementation: parent frame before current environment, and bindings
295 // within a frame ordered by when they were added.
296 assertThat(env.getVariableNames())
297 .containsExactly("a", "c", "b", "x", "z", "y").inOrder();
298 assertThat(env.getGlobals().getTransitiveBindings())
299 .containsExactly("a", 1, "c", 2, "b", 3, "x", 4, "z", 5, "y", 6).inOrder();
300 }
301
302 @Test
303 public void testTransitiveHashCodeDeterminism() throws Exception {
304 // As a proxy for determinism, test that changing the order of imports doesn't change the hash
305 // code (within any one execution).
306 Extension a = new Extension(ImmutableMap.of(), "a123");
307 Extension b = new Extension(ImmutableMap.of(), "b456");
308 Extension c = new Extension(ImmutableMap.of(), "c789");
309 Environment env1 = Environment.builder(Mutability.create("testing1"))
310 .useDefaultSemantics()
311 .setImportedExtensions(ImmutableMap.of("a", a, "b", b, "c", c))
312 .setFileContentHashCode("z")
313 .build();
314 Environment env2 = Environment.builder(Mutability.create("testing2"))
315 .useDefaultSemantics()
316 .setImportedExtensions(ImmutableMap.of("c", c, "b", b, "a", a))
317 .setFileContentHashCode("z")
318 .build();
319 assertThat(env1.getTransitiveContentHashCode()).isEqualTo(env2.getTransitiveContentHashCode());
320 }
321
322 @Test
brandjon2c98fff2018-02-06 01:32:46 -0800323 public void testExtensionEqualityDebugging_RhsIsNull() {
324 assertCheckStateFailsWithMessage(
325 new Extension(ImmutableMap.of(), "abc"),
326 null,
327 "got a null");
328 }
329
330 @Test
331 public void testExtensionEqualityDebugging_RhsHasBadType() {
332 assertCheckStateFailsWithMessage(
333 new Extension(ImmutableMap.of(), "abc"),
334 5,
335 "got a java.lang.Integer");
336 }
337
338 @Test
339 public void testExtensionEqualityDebugging_DifferentBindings() {
340 assertCheckStateFailsWithMessage(
341 new Extension(ImmutableMap.of("w", 1, "x", 2, "y", 3), "abc"),
342 new Extension(ImmutableMap.of("y", 3, "z", 4), "abc"),
343 "in this one but not given one: [w, x]; in given one but not this one: [z]");
344 }
345
346 @Test
347 public void testExtensionEqualityDebugging_DifferentValues() {
348 assertCheckStateFailsWithMessage(
349 new Extension(ImmutableMap.of("x", 1, "y", "foo", "z", true), "abc"),
350 new Extension(ImmutableMap.of("x", 2.0, "y", "foo", "z", false), "abc"),
janakrcfc34322018-03-26 11:10:08 -0700351 "bindings are unequal: x: this one has 1 (class java.lang.Integer, 1), but given one has "
352 + "2.0 (class java.lang.Double, 2.0); z: this one has True (class java.lang.Boolean, "
353 + "true), but given one has False (class java.lang.Boolean, false)");
brandjon2c98fff2018-02-06 01:32:46 -0800354 }
355
356 @Test
357 public void testExtensionEqualityDebugging_DifferentHashes() {
358 assertCheckStateFailsWithMessage(
359 new Extension(ImmutableMap.of(), "abc"),
360 new Extension(ImmutableMap.of(), "xyz"),
361 "transitive content hashes don't match: abc != xyz");
362 }
363
364 private static void assertCheckStateFailsWithMessage(
365 Extension left, Object right, String substring) {
366 IllegalStateException expected =
cushon978cb002018-02-24 14:05:37 -0800367 assertThrows(IllegalStateException.class, () -> left.checkStateEquals(right));
brandjon2c98fff2018-02-06 01:32:46 -0800368 assertThat(expected).hasMessageThat().contains(substring);
369 }
Ulf Adams89f012d2015-02-26 13:39:28 +0000370}