blob: e26234af0cdeacc5d1a54c77b778507f68cb5b6a [file] [log] [blame]
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +00001// Copyright 2006-2015 Google Inc. All rights reserved.
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// 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.
14package com.google.devtools.build.lib.syntax;
15
16import static com.google.common.truth.Truth.assertThat;
17import static org.junit.Assert.fail;
18
Florian Weikert28da3652015-07-01 14:52:30 +000019import com.google.common.truth.Ordered;
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000020import com.google.devtools.build.lib.events.Event;
21import com.google.devtools.build.lib.events.EventCollector;
22import com.google.devtools.build.lib.events.EventHandler;
23import com.google.devtools.build.lib.events.EventKind;
24import com.google.devtools.build.lib.events.util.EventCollectionApparatus;
Florian Weikert28da3652015-07-01 14:52:30 +000025import com.google.devtools.build.lib.packages.PackageFactory;
26import com.google.devtools.build.lib.testutil.TestMode;
27import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000028
29import org.junit.Before;
30
Florian Weikert28da3652015-07-01 14:52:30 +000031import java.util.LinkedList;
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000032import java.util.List;
33
34/**
35 * Base class for test cases that use parsing and evaluation services.
36 */
37public class EvaluationTestCase {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000038
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000039 private EventCollectionApparatus eventCollectionApparatus;
Florian Weikert28da3652015-07-01 14:52:30 +000040 private PackageFactory factory;
41 private TestMode testMode = TestMode.SKYLARK;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000042 protected Environment env;
43 protected Mutability mutability = Mutability.create("test");
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000044
Florian Weikert28da3652015-07-01 14:52:30 +000045 public EvaluationTestCase() {
46 createNewInfrastructure();
47 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000048
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000049 @Before
50 public void setUp() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +000051 createNewInfrastructure();
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000052 env = newEnvironment();
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000053 }
54
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000055 /**
56 * Creates a standard Environment for tests in the BUILD language.
57 * No PythonPreprocessing, mostly empty mutable Environment.
58 */
59 public Environment newBuildEnvironment() {
60 return Environment.builder(mutability)
61 .setGlobals(Environment.BUILD)
62 .setEventHandler(getEventHandler())
63 .setLoadingPhase()
64 .build();
65 }
66
67 /**
68 * Creates an Environment for Skylark with a mostly empty initial environment.
69 * For internal initialization or tests.
70 */
71 public Environment newSkylarkEnvironment() {
72 return Environment.builder(mutability)
73 .setSkylark()
74 .setGlobals(Environment.SKYLARK)
75 .setEventHandler(getEventHandler())
76 .build();
77 }
78
79 /**
80 * Creates a new Environment suitable for the test case. Subclasses may override it
81 * to fit their purpose and e.g. call newBuildEnvironment or newSkylarkEnvironment;
82 * or they may play with the testMode to run tests in either or both kinds of Environment.
83 * Note that all Environment-s may share the same Mutability, so don't close it.
84 * @return a fresh Environment.
85 */
86 public Environment newEnvironment() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +000087 if (testMode == null) {
88 throw new IllegalArgumentException(
89 "TestMode is null. Please set a Testmode via setMode() or set the "
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000090 + "Environment manually by overriding newEnvironment()");
Florian Weikert28da3652015-07-01 14:52:30 +000091 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000092 return testMode.createEnvironment(getEventHandler(), null);
Florian Weikert28da3652015-07-01 14:52:30 +000093 }
94
95 protected void createNewInfrastructure() {
96 eventCollectionApparatus = new EventCollectionApparatus(EventKind.ALL_EVENTS);
97 factory = new PackageFactory(TestRuleClassProvider.getRuleClassProvider());
98 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000099
Florian Weikert28da3652015-07-01 14:52:30 +0000100 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000101 * Sets the specified {@code TestMode} and tries to create the appropriate {@code Environment}
Florian Weikert28da3652015-07-01 14:52:30 +0000102 * @param testMode
103 * @throws Exception
104 */
105 protected void setMode(TestMode testMode) throws Exception {
106 this.testMode = testMode;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000107 env = newEnvironment();
Florian Weikert28da3652015-07-01 14:52:30 +0000108 }
109
110 protected void enableSkylarkMode() throws Exception {
111 setMode(TestMode.SKYLARK);
112 }
113
114 protected void enableBuildMode() throws Exception {
115 setMode(TestMode.BUILD);
116 }
117
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000118 protected EventHandler getEventHandler() {
119 return eventCollectionApparatus.reporter();
120 }
121
Florian Weikert28da3652015-07-01 14:52:30 +0000122 protected PackageFactory getFactory() {
123 return factory;
124 }
125
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000126 public Environment getEnvironment() {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000127 return env;
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000128 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000129
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000130 public boolean isSkylark() {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000131 return env.isSkylark();
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000132 }
133
134 protected List<Statement> parseFile(String... input) {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000135 return env.parseFile(input);
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000136 }
137
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000138 /** Parses an Expression from string without a supporting file */
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000139 Expression parseExpression(String... input) {
Lukacs Berkiafc93a62015-09-17 13:09:03 +0000140 return Parser.parseExpression(env.createLexer(input), getEventHandler());
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000141 }
142
143 public EvaluationTestCase update(String varname, Object value) throws Exception {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000144 env.update(varname, value);
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000145 return this;
146 }
147
148 public Object lookup(String varname) throws Exception {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000149 return env.lookup(varname);
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000150 }
151
152 public Object eval(String... input) throws Exception {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000153 return env.eval(input);
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000154 }
155
156 public void checkEvalError(String msg, String... input) throws Exception {
157 setFailFast(true);
158 try {
159 eval(input);
Laurent Le Brun092f13b2015-08-24 14:50:00 +0000160 fail("Expected error '" + msg + "' but got no error");
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000161 } catch (IllegalArgumentException | EvalException e) {
162 assertThat(e).hasMessage(msg);
163 }
164 }
165
166 public void checkEvalErrorContains(String msg, String... input) throws Exception {
167 try {
168 eval(input);
Laurent Le Brun092f13b2015-08-24 14:50:00 +0000169 fail("Expected error containing '" + msg + "' but got no error");
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000170 } catch (IllegalArgumentException | EvalException e) {
171 assertThat(e.getMessage()).contains(msg);
172 }
173 }
174
175 public void checkEvalErrorStartsWith(String msg, String... input) throws Exception {
176 try {
177 eval(input);
Laurent Le Brun092f13b2015-08-24 14:50:00 +0000178 fail("Expected error starting with '" + msg + "' but got no error");
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000179 } catch (IllegalArgumentException | EvalException e) {
180 assertThat(e.getMessage()).startsWith(msg);
181 }
182 }
183
184 // Forward relevant methods to the EventCollectionApparatus
185 public EvaluationTestCase setFailFast(boolean failFast) {
186 eventCollectionApparatus.setFailFast(failFast);
187 return this;
188 }
189 public EvaluationTestCase assertNoEvents() {
190 eventCollectionApparatus.assertNoEvents();
191 return this;
192 }
193 public EventCollector getEventCollector() {
194 return eventCollectionApparatus.collector();
195 }
196 public Event assertContainsEvent(String expectedMessage) {
197 return eventCollectionApparatus.assertContainsEvent(expectedMessage);
198 }
199 public List<Event> assertContainsEventWithFrequency(String expectedMessage,
200 int expectedFrequency) {
201 return eventCollectionApparatus.assertContainsEventWithFrequency(
202 expectedMessage, expectedFrequency);
203 }
204 public Event assertContainsEventWithWordsInQuotes(String... words) {
205 return eventCollectionApparatus.assertContainsEventWithWordsInQuotes(words);
206 }
207 public EvaluationTestCase clearEvents() {
208 eventCollectionApparatus.collector().clear();
209 return this;
210 }
Florian Weikert28da3652015-07-01 14:52:30 +0000211
212 /**
213 * Encapsulates a separate test which can be executed by a {@code TestMode}
214 */
215 protected interface Testable {
216 public void run() throws Exception;
217 }
218
219 /**
220 * Base class for test cases that run in specific modes (e.g. Build and/or Skylark)
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000221 */
Florian Weikert28da3652015-07-01 14:52:30 +0000222 protected abstract class ModalTestCase {
223 private final SetupActions setup;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000224
225 protected ModalTestCase() {
Florian Weikert28da3652015-07-01 14:52:30 +0000226 setup = new SetupActions();
227 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000228
Florian Weikert28da3652015-07-01 14:52:30 +0000229 /**
230 * Allows the execution of several statements before each following test
231 * @param statements The statement(s) to be executed
232 * @return This {@code ModalTestCase}
233 */
234 public ModalTestCase setUp(String... statements) {
235 setup.registerEval(statements);
236 return this;
237 }
238
239 /**
240 * Allows the update of the specified variable before each following test
241 * @param name The name of the variable that should be updated
242 * @param value The new value of the variable
243 * @return This {@code ModalTestCase}
244 */
245 public ModalTestCase update(String name, Object value) {
246 setup.registerUpdate(name, value);
247 return this;
248 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000249
Florian Weikert28da3652015-07-01 14:52:30 +0000250 /**
251 * Evaluates two parameters and compares their results.
252 * @param statement The statement to be evaluated
253 * @param expectedEvalString The expression of the expected result
254 * @return This {@code ModalTestCase}
255 * @throws Exception
256 */
257 public ModalTestCase testEval(String statement, String expectedEvalString) throws Exception {
258 runTest(createComparisonTestable(statement, expectedEvalString, true));
259 return this;
260 }
261
262 /**
263 * Evaluates the given statement and compares its result to the expected object
264 * @param statement
265 * @param expected
266 * @return This {@code ModalTestCase}
267 * @throws Exception
268 */
269 public ModalTestCase testStatement(String statement, Object expected) throws Exception {
270 runTest(createComparisonTestable(statement, expected, false));
271 return this;
272 }
273
274 /**
275 * Evaluates the given statement and compares its result to the collection of expected objects
276 * without considering their order
277 * @param statement The statement to be evaluated
278 * @param items The expected items
279 * @return This {@code ModalTestCase}
280 * @throws Exception
281 */
282 public ModalTestCase testCollection(String statement, Object... items) throws Exception {
283 runTest(collectionTestable(statement, false, items));
284 return this;
285 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000286
Florian Weikert28da3652015-07-01 14:52:30 +0000287 /**
288 * Evaluates the given statement and compares its result to the collection of expected objects
289 * while considering their order
290 * @param statement The statement to be evaluated
291 * @param items The expected items, in order
292 * @return This {@code ModalTestCase}
293 * @throws Exception
294 */
295 public ModalTestCase testExactOrder(String statement, Object... items) throws Exception {
296 runTest(collectionTestable(statement, true, items));
297 return this;
298 }
299
300 /**
301 * Evaluates the given statement and checks whether the given error message appears
302 * @param expectedError The expected error message
303 * @param statements The statement(s) to be evaluated
304 * @return This ModalTestCase
305 * @throws Exception
306 */
307 public ModalTestCase testIfExactError(String expectedError, String... statements)
308 throws Exception {
309 runTest(errorTestable(true, expectedError, statements));
310 return this;
311 }
312
313 /**
314 * Evaluates the given statement and checks whether an error that contains the expected message
315 * occurs
316 * @param expectedError
317 * @param statements
318 * @return This ModalTestCase
319 * @throws Exception
320 */
321 public ModalTestCase testIfErrorContains(String expectedError, String... statements)
322 throws Exception {
323 runTest(errorTestable(false, expectedError, statements));
324 return this;
325 }
326
327 /**
328 * Looks up the value of the specified variable and compares it to the expected value
329 * @param name
330 * @param expected
331 * @return This ModalTestCase
332 * @throws Exception
333 */
334 public ModalTestCase testLookup(String name, Object expected) throws Exception {
335 runTest(createLookUpTestable(name, expected));
336 return this;
337 }
338
339 /**
340 * Creates a Testable that checks whether the evaluation of the given statement leads to the
341 * expected error
342 * @param statements
343 * @param error
344 * @param exactMatch If true, the error message has to be identical to the expected error
345 * @return An instance of Testable that runs the error check
346 */
347 protected Testable errorTestable(final boolean exactMatch, final String error,
348 final String... statements) {
349 return new Testable() {
350 @Override
351 public void run() throws Exception {
352 if (exactMatch) {
353 checkEvalError(error, statements);
354 } else {
355 checkEvalErrorContains(error, statements);
356 }
357 }
358 };
359 }
360
361 /**
362 * Creates a testable that checks whether the evaluation of the given statement leads to a list
363 * that contains exactly the expected objects
364 * @param statement The statement to be evaluated
365 * @param ordered Determines whether the order of the elements is checked as well
366 * @param expected Expected objects
367 * @return An instance of Testable that runs the check
368 */
369 protected Testable collectionTestable(
370 final String statement, final boolean ordered, final Object... expected) {
371 return new Testable() {
372 @Override
373 public void run() throws Exception {
374 Ordered tmp = assertThat((Iterable<?>) eval(statement)).containsExactly(expected);
375
376 if (ordered) {
377 tmp.inOrder();
378 }
379 }
380 };
381 }
382
383 /**
384 * Creates a testable that compares the evaluation of the given statement to a specified result
385 *
386 * @param statement The statement to be evaluated
387 * @param expected Either the expected object or an expression whose evaluation leads to the
388 * expected object
389 * @param expectedIsExpression Signals whether {@code expected} is an object or an expression
390 * @return An instance of Testable that runs the comparison
391 */
392 protected Testable createComparisonTestable(
393 final String statement, final Object expected, final boolean expectedIsExpression) {
394 return new Testable() {
395 @Override
396 public void run() throws Exception {
397 Object actual = eval(statement);
Florian Weikertf31b9472015-08-04 16:36:58 +0000398 Object realExpected = expected;
Florian Weikert28da3652015-07-01 14:52:30 +0000399
Florian Weikertf31b9472015-08-04 16:36:58 +0000400 // We could also print the actual object and compare the string to the expected
401 // expression, but then the order of elements would matter.
Florian Weikert28da3652015-07-01 14:52:30 +0000402 if (expectedIsExpression) {
Florian Weikertf31b9472015-08-04 16:36:58 +0000403 realExpected = eval((String) expected);
Florian Weikert28da3652015-07-01 14:52:30 +0000404 }
405
Florian Weikertf31b9472015-08-04 16:36:58 +0000406 assertThat(actual).isEqualTo(realExpected);
Florian Weikert28da3652015-07-01 14:52:30 +0000407 }
408 };
409 }
410
411 /**
412 * Creates a Testable that looks up the given variable and compares its value to the expected
413 * value
414 * @param name
415 * @param expected
416 * @return An instance of Testable that does both lookup and comparison
417 */
418 protected Testable createLookUpTestable(final String name, final Object expected) {
419 return new Testable() {
420 @Override
421 public void run() throws Exception {
422 assertThat(lookup(name)).isEqualTo(expected);
423 }
424 };
425 }
426
427 /**
428 * Executes the given Testable
429 * @param testable
430 * @throws Exception
431 */
432 protected void runTest(Testable testable) throws Exception {
433 run(new TestableDecorator(setup, testable));
434 }
435
436 protected abstract void run(Testable testable) throws Exception;
437 }
438
439 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000440 * A simple decorator that allows the execution of setup actions before running
Florian Weikert28da3652015-07-01 14:52:30 +0000441 * a {@code Testable}
442 */
443 class TestableDecorator implements Testable {
444 private final SetupActions setup;
445 private final Testable decorated;
446
447 public TestableDecorator(SetupActions setup, Testable decorated) {
448 this.setup = setup;
449 this.decorated = decorated;
450 }
451
452 /**
453 * Executes all stored actions and updates plus the actual {@code Testable}
454 */
455 @Override
456 public void run() throws Exception {
457 setup.executeAll();
458 decorated.run();
459 }
460 }
461
462 /**
463 * A container for collection actions that should be executed before a test
464 */
465 class SetupActions {
466 private List<Testable> setup;
467
468 public SetupActions() {
469 setup = new LinkedList<>();
470 }
471
472 /**
473 * Registers a variable that has to be updated before a test
474 *
475 * @param name
476 * @param value
477 */
478 public void registerUpdate(final String name, final Object value) {
479 setup.add(new Testable() {
480 @Override
481 public void run() throws Exception {
482 EvaluationTestCase.this.update(name, value);
483 }
484 });
485 }
486
487 /**
488 * Registers a statement for evaluation prior to a test
489 *
490 * @param statements
491 */
492 public void registerEval(final String... statements) {
493 setup.add(new Testable() {
494 @Override
495 public void run() throws Exception {
496 EvaluationTestCase.this.eval(statements);
497 }
498 });
499 }
500
501 /**
502 * Executes all stored actions and updates
503 * @throws Exception
504 */
505 public void executeAll() throws Exception {
506 for (Testable testable : setup) {
507 testable.run();
508 }
509 }
510 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000511
Florian Weikert28da3652015-07-01 14:52:30 +0000512 /**
513 * A class that executes each separate test in both modes (Build and Skylark)
514 */
515 protected class BothModesTest extends ModalTestCase {
516 public BothModesTest() {}
517
518 /**
519 * Executes the given Testable in both Build and Skylark mode
520 */
521 @Override
522 protected void run(Testable testable) throws Exception {
523 enableSkylarkMode();
524 try {
525 testable.run();
526 } catch (Exception e) {
527 throw new Exception("While in Skylark mode", e);
528 }
529
530 enableBuildMode();
531 try {
532 testable.run();
533 } catch (Exception e) {
534 throw new Exception("While in Build mode", e);
535 }
536 }
537 }
538
539 /**
540 * A class that runs all tests in Build mode
541 */
542 protected class BuildTest extends ModalTestCase {
543 public BuildTest() {}
544
545 @Override
546 protected void run(Testable testable) throws Exception {
547 enableBuildMode();
548 testable.run();
549 }
550 }
551
552 /**
553 * A class that runs all tests in Skylark mode
554 */
555 protected class SkylarkTest extends ModalTestCase {
556 public SkylarkTest() {}
557
558 @Override
559 protected void run(Testable testable) throws Exception {
560 enableSkylarkMode();
561 testable.run();
562 }
563 }
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000564}