blob: aee2a985fe6512da8449ea57c8e81e789897d15a [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 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.
14package com.google.devtools.build.lib.syntax;
15
16import static com.google.common.truth.Truth.assertThat;
17
Ulf Adams89f012d2015-02-26 13:39:28 +000018import com.google.common.collect.ImmutableMap;
vladmos46907932017-06-30 14:01:45 +020019import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
20import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +000021import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
22import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
Han-Wen Nienhuysceae8c52015-09-22 16:24:45 +000023import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
Florian Weikert28da3652015-07-01 14:52:30 +000024import com.google.devtools.build.lib.testutil.TestMode;
Andreas Bergmeiera3973402016-12-15 15:33:41 +000025import java.util.Collections;
26import java.util.List;
27import java.util.Map;
Han-Wen Nienhuys33ce2112015-09-25 14:25:38 +000028import org.junit.Before;
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000029import org.junit.Test;
30import org.junit.runner.RunWith;
31import org.junit.runners.JUnit4;
32
Ulf Adams89f012d2015-02-26 13:39:28 +000033/**
34 * Test of evaluation behavior. (Implicitly uses lexer + parser.)
35 */
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000036@RunWith(JUnit4.class)
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000037public class EvaluationTest extends EvaluationTestCase {
Florian Weikertb4c59042015-12-01 10:47:18 +000038
Han-Wen Nienhuys33ce2112015-09-25 14:25:38 +000039 @Before
Florian Weikertb4c59042015-12-01 10:47:18 +000040 public final void setBuildMode() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +000041 super.setMode(TestMode.BUILD);
42 }
Ulf Adams89f012d2015-02-26 13:39:28 +000043
Florian Weikert28da3652015-07-01 14:52:30 +000044 /**
45 * Creates a new instance of {@code ModalTestCase}.
46 *
47 * <p>If a test uses this method, it allows potential subclasses to run the very same test in a
48 * different mode in subclasses
49 */
vladmos6ff634d2017-07-05 10:25:01 -040050 protected ModalTestCase newTest(String... skylarkOptions) {
51 return new BuildTest(skylarkOptions);
Ulf Adams89f012d2015-02-26 13:39:28 +000052 }
53
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000054 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +000055 public void testExprs() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +000056 newTest()
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000057 .testStatement("'%sx' % 'foo' + 'bar1'", "fooxbar1")
58 .testStatement("('%sx' % 'foo') + 'bar2'", "fooxbar2")
59 .testStatement("'%sx' % ('foo' + 'bar3')", "foobar3x")
Florian Weikert28da3652015-07-01 14:52:30 +000060 .testStatement("123 + 456", 579)
61 .testStatement("456 - 123", 333)
62 .testStatement("8 % 3", 2)
brandjonf2ed8582017-06-27 15:05:35 +020063 .testIfErrorContains("unsupported operand type(s) for %: 'int' and 'string'", "3 % 'foo'")
64 .testStatement("-5", -5)
Marwan Tammam66aa4242019-07-24 06:56:08 -070065 .testIfErrorContains("unsupported unary operation: -string", "-'foo'");
Ulf Adams89f012d2015-02-26 13:39:28 +000066 }
67
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000068 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +000069 public void testListExprs() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +000070 newTest().testExactOrder("[1, 2, 3]", 1, 2, 3).testExactOrder("(1, 2, 3)", 1, 2, 3);
Ulf Adams89f012d2015-02-26 13:39:28 +000071 }
72
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000073 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +000074 public void testStringFormatMultipleArgs() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +000075 newTest().testStatement("'%sY%s' % ('X', 'Z')", "XYZ");
Ulf Adams89f012d2015-02-26 13:39:28 +000076 }
77
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +000078 @Test
Francois-Rene Rideau6fc5ee72015-03-12 20:55:17 +000079 public void testConditionalExpressions() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +000080 newTest()
81 .testStatement("1 if True else 2", 1)
82 .testStatement("1 if False else 2", 2)
83 .testStatement("1 + 2 if 3 + 4 else 5 + 6", 3);
Francois-Rene Rideau6fc5ee72015-03-12 20:55:17 +000084
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +000085 setFailFast(false);
86 parseExpression("1 if 2");
Ulf Adamsc708f962015-10-22 12:02:28 +000087 assertContainsError(
Francois-Rene Rideau6fc5ee72015-03-12 20:55:17 +000088 "missing else clause in conditional expression or semicolon before if");
Francois-Rene Rideau6fc5ee72015-03-12 20:55:17 +000089 }
90
91 @Test
Laurent Le Brunac8aae82015-04-16 11:42:55 +000092 public void testListComparison() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +000093 newTest()
94 .testStatement("[] < [1]", true)
95 .testStatement("[1] < [1, 1]", true)
96 .testStatement("[1, 1] < [1, 2]", true)
97 .testStatement("[1, 2] < [1, 2, 3]", true)
98 .testStatement("[1, 2, 3] <= [1, 2, 3]", true)
Laurent Le Brunac8aae82015-04-16 11:42:55 +000099
Florian Weikert28da3652015-07-01 14:52:30 +0000100 .testStatement("['a', 'b'] > ['a']", true)
101 .testStatement("['a', 'b'] >= ['a']", true)
102 .testStatement("['a', 'b'] < ['a']", false)
103 .testStatement("['a', 'b'] <= ['a']", false)
Laurent Le Brunac8aae82015-04-16 11:42:55 +0000104
Florian Weikert28da3652015-07-01 14:52:30 +0000105 .testStatement("('a', 'b') > ('a', 'b')", false)
106 .testStatement("('a', 'b') >= ('a', 'b')", true)
107 .testStatement("('a', 'b') < ('a', 'b')", false)
108 .testStatement("('a', 'b') <= ('a', 'b')", true)
Laurent Le Brunac8aae82015-04-16 11:42:55 +0000109
Florian Weikert28da3652015-07-01 14:52:30 +0000110 .testStatement("[[1, 1]] > [[1, 1], []]", false)
Florian Weikertf31b9472015-08-04 16:36:58 +0000111 .testStatement("[[1, 1]] < [[1, 1], []]", true);
Ulf Adams89f012d2015-02-26 13:39:28 +0000112 }
113
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000114 @Test
Vladimir Moskvadbb34872016-11-07 18:32:59 +0000115 public void testSetComparison() throws Exception {
Vladimir Moskvad200daf2016-12-23 16:35:37 +0000116 newTest().testIfExactError("Cannot compare depsets", "depset([1, 2]) < depset([3, 4])");
Vladimir Moskvadbb34872016-11-07 18:32:59 +0000117 }
118
119 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000120 public void testSumFunction() throws Exception {
Googlera3421e22019-09-26 06:48:32 -0700121 BaseFunction sum =
122 new BaseFunction("sum") {
123 @Override
124 public Object call(
125 List<Object> args,
126 Map<String, Object> kwargs,
127 FuncallExpression ast,
128 StarlarkThread thread) {
129 int sum = 0;
130 for (Object arg : args) {
131 sum += (Integer) arg;
132 }
133 return sum;
134 }
135 };
Ulf Adams89f012d2015-02-26 13:39:28 +0000136
Florian Weikert28da3652015-07-01 14:52:30 +0000137 newTest().update(sum.getName(), sum).testStatement("sum(1, 2, 3, 4, 5, 6)", 21)
138 .testStatement("sum", sum).testStatement("sum(a=1, b=2)", 0);
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000139 }
Ulf Adams89f012d2015-02-26 13:39:28 +0000140
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000141 @Test
142 public void testNotCallInt() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000143 newTest().setUp("sum = 123456").testLookup("sum", 123456)
144 .testIfExactError("'int' object is not callable", "sum(1, 2, 3, 4, 5, 6)")
145 .testStatement("sum", 123456);
Ulf Adams89f012d2015-02-26 13:39:28 +0000146 }
147
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000148 @Test
fzaisere0f13332017-08-14 12:00:51 +0200149 public void testComplexFunctionCall() throws Exception {
150 newTest().setUp("functions = [min, max]", "l = [1,2]")
151 .testEval("(functions[0](l), functions[1](l))", "(1, 2)");
152 }
153
154 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000155 public void testKeywordArgs() throws Exception {
156
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000157 // This function returns the map of keyword arguments passed to it.
Googlera3421e22019-09-26 06:48:32 -0700158 BaseFunction kwargs =
159 new BaseFunction("kwargs") {
160 @Override
161 public Object call(
162 List<Object> args,
163 final Map<String, Object> kwargs,
164 FuncallExpression ast,
165 StarlarkThread thread) {
166 return SkylarkDict.copyOf(thread, kwargs);
167 }
168 };
Ulf Adams89f012d2015-02-26 13:39:28 +0000169
Florian Weikert28da3652015-07-01 14:52:30 +0000170 newTest()
171 .update(kwargs.getName(), kwargs)
172 .testEval(
173 "kwargs(foo=1, bar='bar', wiz=[1,2,3]).items()",
Vladimir Moskva76e31d12016-12-05 16:28:37 +0000174 "[('foo', 1), ('bar', 'bar'), ('wiz', [1, 2, 3])]")
175 .testEval(
176 "kwargs(wiz=[1,2,3], bar='bar', foo=1).items()",
177 "[('wiz', [1, 2, 3]), ('bar', 'bar'), ('foo', 1)]");
Ulf Adams89f012d2015-02-26 13:39:28 +0000178 }
179
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000180 @Test
Laurent Le Brunbd716742015-04-15 11:05:03 +0000181 public void testModulo() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000182 newTest()
183 .testStatement("6 % 2", 0)
184 .testStatement("6 % 4", 2)
185 .testStatement("3 % 6", 3)
186 .testStatement("7 % -4", -1)
187 .testStatement("-7 % 4", 1)
188 .testStatement("-7 % -4", -3)
189 .testIfExactError("integer modulo by zero", "5 % 0");
Laurent Le Brunbd716742015-04-15 11:05:03 +0000190 }
191
192 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000193 public void testMult() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000194 newTest()
195 .testStatement("6 * 7", 42)
196 .testStatement("3 * 'ab'", "ababab")
197 .testStatement("0 * 'ab'", "")
Florian Weikert03b5b572016-11-02 13:34:09 +0000198 .testStatement("'1' + '0' * 5", "100000")
199 .testStatement("'ab' * -4", "")
200 .testStatement("-1 * ''", "");
Ulf Adams89f012d2015-02-26 13:39:28 +0000201 }
202
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000203 @Test
laurentlbc381cf12018-04-11 04:12:14 -0700204 public void testSlashOperatorIsForbidden() throws Exception {
laurentlb387cb022019-01-11 09:39:38 -0800205 newTest().testIfErrorContains("The `/` operator is not allowed.", "5 / 2");
Laurent Le Brun8a528262015-04-15 14:23:35 +0000206 }
207
208 @Test
laurentlb094bb262017-05-19 21:18:25 +0200209 public void testFloorDivision() throws Exception {
210 newTest()
211 .testStatement("6 // 2", 3)
212 .testStatement("6 // 4", 1)
213 .testStatement("3 // 6", 0)
214 .testStatement("7 // -2", -4)
215 .testStatement("-7 // 2", -4)
216 .testStatement("-7 // -2", 3)
217 .testStatement("2147483647 // 2", 1073741823)
laurentlb58dab6c2018-11-12 11:34:30 -0800218 .testIfErrorContains("unsupported operand type(s) for //: 'string' and 'int'", "'str' // 2")
laurentlb094bb262017-05-19 21:18:25 +0200219 .testIfExactError("integer division by zero", "5 // 0");
220 }
221
222 @Test
laurentlb34bbdcf2017-07-03 12:16:15 -0400223 public void testCheckedArithmetic() throws Exception {
laurentlb2195b1c2018-02-16 04:14:46 -0800224 new SkylarkTest()
laurentlb34bbdcf2017-07-03 12:16:15 -0400225 .testIfErrorContains("integer overflow", "2000000000 + 2000000000")
226 .testIfErrorContains("integer overflow", "1234567890 * 987654321")
227 .testIfErrorContains("integer overflow", "- 2000000000 - 2000000000")
228
229 // literal 2147483648 is not allowed, so we compute it
230 .setUp("minint = - 2147483647 - 1")
231 .testIfErrorContains("integer overflow", "-minint");
232 }
233
234 @Test
Laurent Le Brun8a528262015-04-15 14:23:35 +0000235 public void testOperatorPrecedence() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000236 newTest()
237 .testStatement("2 + 3 * 4", 14)
laurentlb58dab6c2018-11-12 11:34:30 -0800238 .testStatement("2 + 3 // 4", 2)
239 .testStatement("2 * 3 + 4 // -2", 4);
Laurent Le Brun8a528262015-04-15 14:23:35 +0000240 }
241
242 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000243 public void testConcatStrings() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000244 newTest().testStatement("'foo' + 'bar'", "foobar");
Ulf Adams89f012d2015-02-26 13:39:28 +0000245 }
246
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000247 @SuppressWarnings("unchecked")
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000248 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000249 public void testConcatLists() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000250 // TODO(fwe): cannot be handled by current testing suite
Ulf Adams89f012d2015-02-26 13:39:28 +0000251 // list
252 Object x = eval("[1,2] + [3,4]");
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000253 assertThat((Iterable<Object>) x).containsExactly(1, 2, 3, 4).inOrder();
Googlera3421e22019-09-26 06:48:32 -0700254 assertThat(x).isEqualTo(MutableList.of(thread, 1, 2, 3, 4));
lberkiaea56b32017-05-30 12:35:33 +0200255 assertThat(EvalUtils.isImmutable(x)).isFalse();
Ulf Adams89f012d2015-02-26 13:39:28 +0000256
257 // tuple
258 x = eval("(1,2) + (3,4)");
lberkiaea56b32017-05-30 12:35:33 +0200259 assertThat(x).isEqualTo(Tuple.of(1, 2, 3, 4));
260 assertThat(EvalUtils.isImmutable(x)).isTrue();
Ulf Adams89f012d2015-02-26 13:39:28 +0000261
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000262 checkEvalError("unsupported operand type(s) for +: 'tuple' and 'list'",
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000263 "(1,2) + [3,4]"); // list + tuple
Ulf Adams89f012d2015-02-26 13:39:28 +0000264 }
265
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000266 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000267 public void testListComprehensions() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000268 newTest()
269 .testExactOrder("['foo/%s.java' % x for x in []]")
270 .testExactOrder("['foo/%s.java' % y for y in ['bar', 'wiz', 'quux']]", "foo/bar.java",
271 "foo/wiz.java", "foo/quux.java")
laurentlbc3a1af62017-06-16 14:37:43 +0200272 .testExactOrder("['%s/%s.java' % (z, t) for z in ['foo', 'bar'] "
Florian Weikert28da3652015-07-01 14:52:30 +0000273 + "for t in ['baz', 'wiz', 'quux']]",
274 "foo/baz.java",
275 "foo/wiz.java",
276 "foo/quux.java",
277 "bar/baz.java",
278 "bar/wiz.java",
279 "bar/quux.java")
laurentlbc3a1af62017-06-16 14:37:43 +0200280 .testExactOrder("['%s/%s.java' % (b, b) for a in ['foo', 'bar'] "
Florian Weikert28da3652015-07-01 14:52:30 +0000281 + "for b in ['baz', 'wiz', 'quux']]",
282 "baz/baz.java",
283 "wiz/wiz.java",
284 "quux/quux.java",
285 "baz/baz.java",
286 "wiz/wiz.java",
287 "quux/quux.java")
laurentlbc3a1af62017-06-16 14:37:43 +0200288 .testExactOrder("['%s/%s.%s' % (c, d, e) for c in ['foo', 'bar'] "
289 + "for d in ['baz', 'wiz', 'quux'] for e in ['java', 'cc']]",
Florian Weikert28da3652015-07-01 14:52:30 +0000290 "foo/baz.java",
291 "foo/baz.cc",
292 "foo/wiz.java",
293 "foo/wiz.cc",
294 "foo/quux.java",
295 "foo/quux.cc",
296 "bar/baz.java",
297 "bar/baz.cc",
298 "bar/wiz.java",
299 "bar/wiz.cc",
300 "bar/quux.java",
laurentlbc3a1af62017-06-16 14:37:43 +0200301 "bar/quux.cc")
302 .testExactOrder("[i for i in (1, 2)]", 1, 2)
303 .testExactOrder("[i for i in [2, 3] or [1, 2]]", 2, 3);
Ulf Adams89f012d2015-02-26 13:39:28 +0000304 }
305
Laurent Le Brun741824b2015-03-20 15:10:19 +0000306 @Test
Laurent Le Brun52021662015-05-18 09:28:26 +0000307 public void testNestedListComprehensions() throws Exception {
laurentlb0d5d3a52018-12-18 05:26:35 -0800308 newTest()
309 .testExactOrder("li = [[1, 2], [3, 4]]\n" + "[j for i in li for j in i]", 1, 2, 3, 4)
310 .testExactOrder(
311 "input = [['abc'], ['def', 'ghi']]\n"
312 + "['%s %s' % (b, c) for a in input for b in a for c in b.elems()]",
313 "abc a", "abc b", "abc c", "def d", "def e", "def f", "ghi g", "ghi h", "ghi i");
Laurent Le Brun52021662015-05-18 09:28:26 +0000314 }
315
316 @Test
Laurent Le Brun741824b2015-03-20 15:10:19 +0000317 public void testListComprehensionsMultipleVariables() throws Exception {
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000318 newTest().testEval("[x + y for x, y in [(1, 2), (3, 4)]]", "[3, 7]")
319 .testEval("[z + t for (z, t) in [[1, 2], [3, 4]]]", "[3, 7]");
Laurent Le Brun741824b2015-03-20 15:10:19 +0000320 }
321
322 @Test
323 public void testListComprehensionsMultipleVariablesFail() throws Exception {
brandjon2b51f782017-07-25 21:05:04 +0200324 newTest().testIfErrorContains(
325 "assignment length mismatch: left-hand side has length 3, but right-hand side evaluates to "
326 + "value of length 2",
Florian Weikert28da3652015-07-01 14:52:30 +0000327 "[x + y for x, y, z in [(1, 2), (3, 4)]]").testIfExactError(
328 "type 'int' is not a collection", "[x + y for x, y in (1, 2)]");
Laurent Le Brun741824b2015-03-20 15:10:19 +0000329 }
330
331 @Test
Laurent Le Brunb4c54742015-05-18 13:11:05 +0000332 public void testListComprehensionsWithFiltering() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000333 newTest()
334 .setUp("range3 = [0, 1, 2]")
335 .testEval("[a for a in (4, None, 2, None, 1) if a != None]", "[4, 2, 1]")
336 .testEval("[b+c for b in [0, 1, 2] for c in [0, 1, 2] if b + c > 2]", "[3, 3, 4]")
337 .testEval("[d+e for d in range3 if d % 2 == 1 for e in range3]", "[1, 2, 3]")
338 .testEval("[[f,g] for f in [0, 1, 2, 3, 4] if f for g in [5, 6, 7, 8] if f * g % 12 == 0 ]",
339 "[[2, 6], [3, 8], [4, 6]]")
340 .testEval("[h for h in [4, 2, 0, 1] if h]", "[4, 2, 1]");
Laurent Le Brunb4c54742015-05-18 13:11:05 +0000341 }
342
343 @Test
344 public void testListComprehensionDefinitionOrder() throws Exception {
laurentlb3a979f72018-11-20 07:25:11 -0800345 new BuildTest()
346 .testIfErrorContains("name 'y' is not defined", "[x for x in (1, 2) if y for y in (3, 4)]");
347
348 new SkylarkTest()
349 .testIfErrorContains(
350 "local variable 'y' is referenced before assignment",
351 "[x for x in (1, 2) if y for y in (3, 4)]");
Laurent Le Brunb4c54742015-05-18 13:11:05 +0000352 }
353
354 @Test
Laurent Le Brun741824b2015-03-20 15:10:19 +0000355 public void testTupleDestructuring() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000356 newTest()
357 .setUp("a, b = 1, 2")
358 .testLookup("a", 1)
359 .testLookup("b", 2)
360 .setUp("c, d = {'key1':2, 'key2':3}")
361 .testLookup("c", "key1")
362 .testLookup("d", "key2");
Laurent Le Brun741824b2015-03-20 15:10:19 +0000363 }
364
365 @Test
Laurent Le Brun29ad8622015-09-18 10:45:07 +0000366 public void testSingleTuple() throws Exception {
Laurent Le Brunb639ca82017-01-17 11:18:23 +0000367 newTest().setUp("(a,) = [1]").testLookup("a", 1);
Laurent Le Brun29ad8622015-09-18 10:45:07 +0000368 }
369
370 @Test
Laurent Le Brun2e78d612015-04-15 09:06:46 +0000371 public void testHeterogeneousDict() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000372 newTest().setUp("d = {'str': 1, 2: 3}", "a = d['str']", "b = d[2]").testLookup("a", 1)
373 .testLookup("b", 3);
Laurent Le Brun2e78d612015-04-15 09:06:46 +0000374 }
375
376 @Test
Laurent Le Brun6824d862015-09-11 13:51:41 +0000377 public void testAccessDictWithATupleKey() throws Exception {
378 newTest().setUp("x = {(1, 2): 3}[1, 2]").testLookup("x", 3);
379 }
380
381 @Test
laurentlb8880cf22017-06-23 16:05:36 +0200382 public void testDictWithDuplicatedKey() throws Exception {
laurentlb2195b1c2018-02-16 04:14:46 -0800383 new SkylarkTest()
laurentlb8880cf22017-06-23 16:05:36 +0200384 .testIfErrorContains(
385 "Duplicated key \"str\" when creating dictionary", "{'str': 1, 'x': 2, 'str': 3}");
386 }
387
388 @Test
Laurent Le Brun741824b2015-03-20 15:10:19 +0000389 public void testRecursiveTupleDestructuring() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000390 newTest()
391 .setUp("((a, b), (c, d)) = [(1, 2), (3, 4)]")
392 .testLookup("a", 1)
393 .testLookup("b", 2)
394 .testLookup("c", 3)
395 .testLookup("d", 4);
Laurent Le Brun741824b2015-03-20 15:10:19 +0000396 }
397
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000398 @Test
laurentlbee250c62017-09-07 19:17:52 +0200399 public void testListComprehensionAtTopLevel() throws Exception {
400 // It is allowed to have a loop variable with the same name as a global variable.
401 newTest().update("x", 42).setUp("y = [x + 1 for x in [1,2,3]]")
402 .testExactOrder("y", 2, 3, 4);
Ulf Adams89f012d2015-02-26 13:39:28 +0000403 }
404
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000405 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000406 public void testDictComprehensions() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000407 newTest()
408 .testStatement("{a : a for a in []}", Collections.emptyMap())
409 .testStatement("{b : b for b in [1, 2]}", ImmutableMap.of(1, 1, 2, 2))
410 .testStatement("{c : 'v_' + c for c in ['a', 'b']}",
411 ImmutableMap.of("a", "v_a", "b", "v_b"))
412 .testStatement("{'k_' + d : d for d in ['a', 'b']}",
413 ImmutableMap.of("k_a", "a", "k_b", "b"))
414 .testStatement("{'k_' + e : 'v_' + e for e in ['a', 'b']}",
415 ImmutableMap.of("k_a", "v_a", "k_b", "v_b"))
416 .testStatement("{x+y : x*y for x, y in [[2, 3]]}", ImmutableMap.of(5, 6));
Ulf Adams89f012d2015-02-26 13:39:28 +0000417 }
418
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000419 @Test
Laurent Le Brun2e78d612015-04-15 09:06:46 +0000420 public void testDictComprehensionOnNonIterable() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000421 newTest().testIfExactError("type 'int' is not iterable", "{k : k for k in 3}");
Laurent Le Brun2e78d612015-04-15 09:06:46 +0000422 }
423
424 @Test
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000425 public void testDictComprehension_ManyClauses() throws Exception {
426 new SkylarkTest().testStatement(
427 "{x : x * y for x in range(1, 10) if x % 2 == 0 for y in range(1, 10) if y == x}",
428 ImmutableMap.of(2, 4, 4, 16, 6, 36, 8, 64));
429 }
430
431 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000432 public void testDictComprehensions_MultipleKey() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000433 newTest().testStatement("{x : x for x in [1, 2, 1]}", ImmutableMap.of(1, 1, 2, 2))
434 .testStatement("{y : y for y in ['ab', 'c', 'a' + 'b']}",
435 ImmutableMap.of("ab", "ab", "c", "c"));
Ulf Adams89f012d2015-02-26 13:39:28 +0000436 }
437
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000438 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000439 public void testListConcatenation() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000440 newTest()
Googlera3421e22019-09-26 06:48:32 -0700441 .testStatement("[1, 2] + [3, 4]", MutableList.of(thread, 1, 2, 3, 4))
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000442 .testStatement("(1, 2) + (3, 4)", Tuple.of(1, 2, 3, 4))
Googlera3421e22019-09-26 06:48:32 -0700443 .testIfExactError(
444 "unsupported operand type(s) for +: 'list' and 'tuple'", "[1, 2] + (3, 4)")
445 .testIfExactError(
446 "unsupported operand type(s) for +: 'tuple' and 'list'", "(1, 2) + [3, 4]");
Ulf Adams89f012d2015-02-26 13:39:28 +0000447 }
448
Andreas Bergmeiera3973402016-12-15 15:33:41 +0000449 @Test
450 public void testListMultiply() throws Exception {
451 newTest()
Googlera3421e22019-09-26 06:48:32 -0700452 .testStatement("[1, 2, 3] * 1", MutableList.of(thread, 1, 2, 3))
453 .testStatement("[1, 2] * 2", MutableList.of(thread, 1, 2, 1, 2))
454 .testStatement("[1, 2] * 3", MutableList.of(thread, 1, 2, 1, 2, 1, 2))
455 .testStatement("[1, 2] * 4", MutableList.of(thread, 1, 2, 1, 2, 1, 2, 1, 2))
456 .testStatement("[8] * 5", MutableList.of(thread, 8, 8, 8, 8, 8))
brandjona2d101a2017-08-01 18:54:28 +0200457 .testStatement("[ ] * 10", MutableList.empty())
458 .testStatement("[1, 2] * 0", MutableList.empty())
459 .testStatement("[1, 2] * -4", MutableList.empty())
Googlera3421e22019-09-26 06:48:32 -0700460 .testStatement("2 * [1, 2]", MutableList.of(thread, 1, 2, 1, 2))
brandjona2d101a2017-08-01 18:54:28 +0200461 .testStatement("10 * []", MutableList.empty())
laurentlb17f8d4e2018-05-24 07:32:52 -0700462 .testStatement("0 * [1, 2]", MutableList.empty())
brandjona2d101a2017-08-01 18:54:28 +0200463 .testStatement("-4 * [1, 2]", MutableList.empty());
Andreas Bergmeiera3973402016-12-15 15:33:41 +0000464 }
465
brandjon3d403602017-08-06 20:01:01 +0200466 @Test
467 public void testTupleMultiply() throws Exception {
468 newTest()
469 .testStatement("(1, 2, 3) * 1", Tuple.of(1, 2, 3))
470 .testStatement("(1, 2) * 2", Tuple.of(1, 2, 1, 2))
471 .testStatement("(1, 2) * 3", Tuple.of(1, 2, 1, 2, 1, 2))
472 .testStatement("(1, 2) * 4", Tuple.of(1, 2, 1, 2, 1, 2, 1, 2))
473 .testStatement("(8,) * 5", Tuple.of(8, 8, 8, 8, 8))
474 .testStatement("( ) * 10", Tuple.empty())
475 .testStatement("(1, 2) * 0", Tuple.empty())
476 .testStatement("(1, 2) * -4", Tuple.empty())
laurentlb17f8d4e2018-05-24 07:32:52 -0700477 .testStatement("2 * (1, 2)", Tuple.of(1, 2, 1, 2))
brandjon3d403602017-08-06 20:01:01 +0200478 .testStatement("10 * ()", Tuple.empty())
laurentlb17f8d4e2018-05-24 07:32:52 -0700479 .testStatement("0 * (1, 2)", Tuple.empty())
brandjon3d403602017-08-06 20:01:01 +0200480 .testStatement("-4 * (1, 2)", Tuple.empty());
481 }
482
Greg Estrenb3dece02015-05-14 17:18:41 +0000483 @SuppressWarnings("unchecked")
484 @Test
485 public void testSelectorListConcatenation() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000486 // TODO(fwe): cannot be handled by current testing suite
Greg Estrenb3dece02015-05-14 17:18:41 +0000487 SelectorList x = (SelectorList) eval("select({'foo': ['FOO'], 'bar': ['BAR']}) + []");
488 List<Object> elements = x.getElements();
lberkiaea56b32017-05-30 12:35:33 +0200489 assertThat(elements).hasSize(2);
Greg Estrenb3dece02015-05-14 17:18:41 +0000490 assertThat(elements.get(0)).isInstanceOf(SelectorValue.class);
Florian Weikert28da3652015-07-01 14:52:30 +0000491 assertThat((Iterable<Object>) elements.get(1)).isEmpty();
Greg Estrenb3dece02015-05-14 17:18:41 +0000492 }
493
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000494 @Test
Laurent Le Bruna58dcd12015-11-05 13:51:53 +0000495 public void testAddSelectIncompatibleType() throws Exception {
496 newTest()
497 .testIfErrorContains(
498 "'+' operator applied to incompatible types (select of list, int)",
499 "select({'foo': ['FOO'], 'bar': ['BAR']}) + 1");
500 }
501
502 @Test
503 public void testAddSelectIncompatibleType2() throws Exception {
504 newTest()
505 .testIfErrorContains(
506 "'+' operator applied to incompatible types (select of list, select of int)",
507 "select({'foo': ['FOO']}) + select({'bar': 2})");
508 }
509
510 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000511 public void testListComprehensionFailsOnNonSequence() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000512 newTest().testIfErrorContains("type 'int' is not iterable", "[x + 1 for x in 123]");
Ulf Adams89f012d2015-02-26 13:39:28 +0000513 }
514
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000515 @Test
laurentlba752d8b2017-07-12 22:12:04 +0200516 public void testListComprehensionOnStringIsForbidden() throws Exception {
laurentlb26f843d2019-02-20 06:44:05 -0800517 newTest().testIfErrorContains("type 'string' is not iterable", "[x for x in 'abc']");
Ulf Adams89f012d2015-02-26 13:39:28 +0000518 }
519
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000520 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000521 public void testInvalidAssignment() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000522 newTest().testIfErrorContains(
Jon Brandvein162f9eb2016-11-10 00:22:43 +0000523 "cannot assign to 'x + 1'", "x + 1 = 2");
Ulf Adams89f012d2015-02-26 13:39:28 +0000524 }
525
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000526 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000527 public void testListComprehensionOnDictionary() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000528 newTest().testExactOrder("val = ['var_' + n for n in {'a':1,'b':2}] ; val", "var_a", "var_b");
Ulf Adams89f012d2015-02-26 13:39:28 +0000529 }
530
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000531 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000532 public void testListComprehensionOnDictionaryCompositeExpression() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000533 new BuildTest()
534 .setUp("d = {1:'a',2:'b'}", "l = [d[x] for x in d]")
Googlera3421e22019-09-26 06:48:32 -0700535 .testLookup("l", MutableList.of(thread, "a", "b"));
Ulf Adams89f012d2015-02-26 13:39:28 +0000536 }
537
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000538 @Test
Jon Brandvein79aa7792016-07-25 16:57:54 +0000539 public void testListComprehensionUpdate() throws Exception {
540 new BuildTest()
541 .setUp("xs = [1, 2, 3]")
Jon Brandvein65e3ae62016-09-27 19:57:40 +0000542 .testIfErrorContains("trying to mutate a locked object",
543 "[xs.append(4) for x in xs]");
544 }
545
546 @Test
547 public void testNestedListComprehensionUpdate() throws Exception {
548 new BuildTest()
549 .setUp("xs = [1, 2, 3]")
550 .testIfErrorContains("trying to mutate a locked object",
551 "[xs.append(4) for x in xs for y in xs]");
552 }
553
554 @Test
555 public void testListComprehensionUpdateInClause() throws Exception {
556 new BuildTest()
557 .setUp("xs = [1, 2, 3]")
558 .testIfErrorContains("trying to mutate a locked object",
559 // Use short-circuiting to produce valid output in the event
560 // the exception is not raised.
561 "[y for x in xs for y in (xs.append(4) or xs)]");
562 }
563
564 @Test
565 public void testDictComprehensionUpdate() throws Exception {
566 new BuildTest()
567 .setUp("xs = {1:1, 2:2, 3:3}")
568 .testIfErrorContains("trying to mutate a locked object",
569 "[xs.popitem() for x in xs]");
Jon Brandvein79aa7792016-07-25 16:57:54 +0000570 }
571
572 @Test
Laurent Le Brunab0ca1a2015-03-31 17:13:25 +0000573 public void testInOperator() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000574 newTest()
575 .testStatement("'b' in ['a', 'b']", Boolean.TRUE)
576 .testStatement("'c' in ['a', 'b']", Boolean.FALSE)
577 .testStatement("'b' in ('a', 'b')", Boolean.TRUE)
578 .testStatement("'c' in ('a', 'b')", Boolean.FALSE)
579 .testStatement("'b' in {'a' : 1, 'b' : 2}", Boolean.TRUE)
580 .testStatement("'c' in {'a' : 1, 'b' : 2}", Boolean.FALSE)
581 .testStatement("1 in {'a' : 1, 'b' : 2}", Boolean.FALSE)
582 .testStatement("'b' in 'abc'", Boolean.TRUE)
583 .testStatement("'d' in 'abc'", Boolean.FALSE);
Ulf Adams89f012d2015-02-26 13:39:28 +0000584 }
585
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000586 @Test
Laurent Le Brune3f4ed72015-05-08 14:47:26 +0000587 public void testNotInOperator() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000588 newTest()
589 .testStatement("'b' not in ['a', 'b']", Boolean.FALSE)
590 .testStatement("'c' not in ['a', 'b']", Boolean.TRUE)
591 .testStatement("'b' not in ('a', 'b')", Boolean.FALSE)
592 .testStatement("'c' not in ('a', 'b')", Boolean.TRUE)
593 .testStatement("'b' not in {'a' : 1, 'b' : 2}", Boolean.FALSE)
594 .testStatement("'c' not in {'a' : 1, 'b' : 2}", Boolean.TRUE)
595 .testStatement("1 not in {'a' : 1, 'b' : 2}", Boolean.TRUE)
596 .testStatement("'b' not in 'abc'", Boolean.FALSE)
597 .testStatement("'d' not in 'abc'", Boolean.TRUE);
Laurent Le Brune3f4ed72015-05-08 14:47:26 +0000598 }
599
600 @Test
Laurent Le Brunab0ca1a2015-03-31 17:13:25 +0000601 public void testInFail() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000602 newTest()
Laurent Le Brun83514642017-03-02 14:12:53 +0000603 .testIfErrorContains(
604 "'in <string>' requires string as left operand, not 'int'", "1 in '123'")
605 .testIfErrorContains("'int' is not iterable. in operator only works on ", "'a' in 1");
Ulf Adams89f012d2015-02-26 13:39:28 +0000606 }
607
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000608 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000609 public void testInCompositeForPrecedence() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000610 newTest().testStatement("not 'a' in ['a'] or 0", 0);
Ulf Adams89f012d2015-02-26 13:39:28 +0000611 }
612
vladmos46907932017-06-30 14:01:45 +0200613 private SkylarkValue createObjWithStr() {
614 return new SkylarkValue() {
615 @Override
616 public void repr(SkylarkPrinter printer) {
vladmos6ff634d2017-07-05 10:25:01 -0400617 printer.append("<str marker>");
618 }
vladmos46907932017-06-30 14:01:45 +0200619 };
620 }
621
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000622 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000623 public void testPercOnObject() throws Exception {
vladmoscd6d8ae2017-10-12 15:35:17 +0200624 newTest()
vladmos46907932017-06-30 14:01:45 +0200625 .update("obj", createObjWithStr())
vladmos6ff634d2017-07-05 10:25:01 -0400626 .testStatement("'%s' % obj", "<str marker>");
vladmos46907932017-06-30 14:01:45 +0200627 newTest()
vladmosd4cc4b62017-07-14 19:04:55 +0200628 .update("unknown", new Object())
629 .testStatement("'%s' % unknown", "<unknown object java.lang.Object>");
Ulf Adams89f012d2015-02-26 13:39:28 +0000630 }
631
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000632 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000633 public void testPercOnObjectList() throws Exception {
vladmoscd6d8ae2017-10-12 15:35:17 +0200634 newTest()
vladmos46907932017-06-30 14:01:45 +0200635 .update("obj", createObjWithStr())
vladmos6ff634d2017-07-05 10:25:01 -0400636 .testStatement("'%s %s' % (obj, obj)", "<str marker> <str marker>");
vladmos46907932017-06-30 14:01:45 +0200637 newTest()
vladmosd4cc4b62017-07-14 19:04:55 +0200638 .update("unknown", new Object())
639 .testStatement(
640 "'%s %s' % (unknown, unknown)",
641 "<unknown object java.lang.Object> <unknown object java.lang.Object>");
Ulf Adams89f012d2015-02-26 13:39:28 +0000642 }
643
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000644 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000645 public void testPercOnObjectInvalidFormat() throws Exception {
vladmoscd6d8ae2017-10-12 15:35:17 +0200646 newTest()
vladmos6ff634d2017-07-05 10:25:01 -0400647 .update("obj", createObjWithStr())
648 .testIfExactError("invalid argument <str marker> for format pattern %d", "'%d' % obj");
Ulf Adams89f012d2015-02-26 13:39:28 +0000649 }
650
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000651 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000652 public void testDictKeys() throws Exception {
Florian Weikert28da3652015-07-01 14:52:30 +0000653 newTest().testExactOrder("v = {'a': 1}.keys() + ['b', 'c'] ; v", "a", "b", "c");
Ulf Adams89f012d2015-02-26 13:39:28 +0000654 }
655
Han-Wen Nienhuysccf19ea2015-02-27 15:53:24 +0000656 @Test
Ulf Adams89f012d2015-02-26 13:39:28 +0000657 public void testDictKeysTooManyArgs() throws Exception {
cparsons03035142019-01-15 13:35:22 -0800658 newTest()
659 .testIfExactError(
660 "expected no more than 0 positional arguments, but got 1, "
661 + "for call to method keys() of 'dict'",
662 "{'a': 1}.keys('abc')");
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000663 }
664
665 @Test
666 public void testDictKeysTooManyKeyArgs() throws Exception {
cparsons03035142019-01-15 13:35:22 -0800667 newTest()
668 .testIfExactError(
669 "unexpected keyword 'arg', for call to method keys() of 'dict'",
670 "{'a': 1}.keys(arg='abc')");
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000671 }
672
673 @Test
674 public void testDictKeysDuplicateKeyArgs() throws Exception {
fzaiser8c27a892017-08-11 17:26:44 +0200675 newTest().testIfExactError("duplicate keywords 'arg', 'k' in call to {\"a\": 1}.keys",
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000676 "{'a': 1}.keys(arg='abc', arg='def', k=1, k=2)");
677 }
678
679 @Test
680 public void testArgBothPosKey() throws Exception {
cparsons03035142019-01-15 13:35:22 -0800681 newTest()
682 .testIfErrorContains(
cparsons65ecfea2019-08-28 14:37:23 -0700683 "got multiple values for keyword argument 'base', "
684 + "for call to function int(x, base = unbound)",
685 "int('2', 3, base=3)");
Ulf Adams89f012d2015-02-26 13:39:28 +0000686 }
laurentlbab062912019-04-12 07:00:20 -0700687
688 @Test
laurentlbed633332019-04-12 16:34:08 -0700689 public void testStaticNameResolution() throws Exception {
laurentlbfdc2da72019-06-13 08:43:24 -0700690 newTest().testIfErrorContains("name 'foo' is not defined", "[foo for x in []]");
laurentlbed633332019-04-12 16:34:08 -0700691 }
692
693 @Test
laurentlbab062912019-04-12 07:00:20 -0700694 public void testDefInBuild() throws Exception {
695 new BuildTest()
696 .testIfErrorContains(
697 "function definitions are not allowed in BUILD files", "def func(): pass");
698 }
699
700 @Test
701 public void testForStatementForbiddenInBuild() throws Exception {
702 new BuildTest().testIfErrorContains("for loops are not allowed", "for _ in []: pass");
703 }
704
705 @Test
706 public void testIfStatementForbiddenInBuild() throws Exception {
laurentlbcc7a6e02019-05-22 14:30:47 -0700707 new BuildTest().testIfErrorContains("if statements are not allowed", "if False: pass");
laurentlbab062912019-04-12 07:00:20 -0700708 }
709
710 @Test
711 public void testKwargsForbiddenInBuild() throws Exception {
712 new BuildTest()
laurentlbcc7a6e02019-05-22 14:30:47 -0700713 .testIfErrorContains("**kwargs arguments are not allowed in BUILD files", "print(**dict)");
laurentlb3b7e2942019-06-13 09:51:47 -0700714
715 new BuildTest()
716 .testIfErrorContains(
717 "**kwargs arguments are not allowed in BUILD files", "len(dict(**{'a': 1}))");
laurentlbab062912019-04-12 07:00:20 -0700718 }
719
720 @Test
721 public void testArgsForbiddenInBuild() throws Exception {
722 new BuildTest()
laurentlbcc7a6e02019-05-22 14:30:47 -0700723 .testIfErrorContains("*args arguments are not allowed in BUILD files", "print(*['a'])");
laurentlbab062912019-04-12 07:00:20 -0700724 }
Ulf Adams89f012d2015-02-26 13:39:28 +0000725}