// Copyright 2015 The Bazel Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.syntax; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.syntax.SkylarkList.MutableList; import com.google.devtools.build.lib.syntax.SkylarkList.Tuple; import java.util.LinkedHashMap; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.Arrays; import java.util.IllegalFormatException; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * Test properties of the evaluator's datatypes and utility functions * without actually creating any parse trees. */ @RunWith(JUnit4.class) public class PrinterTest { @Test public void testPrinter() throws Exception { // Note that prettyPrintValue and printValue only differ on behaviour of // labels and strings at toplevel. assertEquals("foo\nbar", Printer.str("foo\nbar")); assertEquals("\"foo\\nbar\"", Printer.repr("foo\nbar")); assertEquals("'", Printer.str("'")); assertEquals("\"'\"", Printer.repr("'")); assertEquals("\"", Printer.str("\"")); assertEquals("\"\\\"\"", Printer.repr("\"")); assertEquals("3", Printer.str(3)); assertEquals("3", Printer.repr(3)); assertEquals("None", Printer.repr(Runtime.NONE)); assertEquals("//x:x", Printer.str( Label.parseAbsolute("//x"))); assertEquals("\"//x:x\"", Printer.repr( Label.parseAbsolute("//x"))); List<?> list = MutableList.of(null, "foo", "bar"); List<?> tuple = Tuple.of("foo", "bar"); assertEquals("(1, [\"foo\", \"bar\"], 3)", Printer.str(Tuple.of(1, list, 3))); assertEquals("(1, [\"foo\", \"bar\"], 3)", Printer.repr(Tuple.of(1, list, 3))); assertEquals("[1, (\"foo\", \"bar\"), 3]", Printer.str(MutableList.of(null, 1, tuple, 3))); assertEquals("[1, (\"foo\", \"bar\"), 3]", Printer.repr(MutableList.of(null, 1, tuple, 3))); Map<Object, Object> dict = ImmutableMap.<Object, Object>of( 1, tuple, 2, list, "foo", MutableList.of(null)); assertEquals("{1: (\"foo\", \"bar\"), 2: [\"foo\", \"bar\"], \"foo\": []}", Printer.str(dict)); assertEquals("{1: (\"foo\", \"bar\"), 2: [\"foo\", \"bar\"], \"foo\": []}", Printer.repr(dict)); } private void checkFormatPositionalFails(String errorMessage, String format, Object... arguments) { try { Printer.format(format, arguments); fail(); } catch (IllegalFormatException e) { assertThat(e).hasMessage(errorMessage); } } @Test public void testOutputOrderOfMap() throws Exception { Map<Object, Object> map = new LinkedHashMap<>(); map.put(5, 5); map.put(3, 3); map.put("foo", 42); map.put(7, "bar"); assertThat(Printer.str(map)).isEqualTo("{5: 5, 3: 3, \"foo\": 42, 7: \"bar\"}"); } @Test public void testFormatPositional() throws Exception { assertEquals("foo 3", Printer.formatToString("%s %d", Tuple.of("foo", 3))); assertEquals("foo 3", Printer.format("%s %d", "foo", 3)); // Note: formatToString doesn't perform scalar x -> (x) conversion; // The %-operator is responsible for that. assertThat(Printer.formatToString("", Tuple.of())).isEmpty(); assertEquals("foo", Printer.format("%s", "foo")); assertEquals("3.14159", Printer.format("%s", 3.14159)); checkFormatPositionalFails("not all arguments converted during string formatting", "%s", 1, 2, 3); assertEquals("%foo", Printer.format("%%%s", "foo")); checkFormatPositionalFails("not all arguments converted during string formatting", "%%s", "foo"); checkFormatPositionalFails("unsupported format character \" \" at index 1 in \"% %s\"", "% %s", "foo"); assertEquals("[1, 2, 3]", Printer.format("%s", MutableList.of(null, 1, 2, 3))); assertEquals("(1, 2, 3)", Printer.format("%s", Tuple.of(1, 2, 3))); assertEquals("[]", Printer.format("%s", MutableList.of(null))); assertEquals("()", Printer.format("%s", Tuple.of())); assertEquals("% 1 \"2\" 3", Printer.format("%% %d %r %s", 1, "2", "3")); checkFormatPositionalFails( "invalid argument \"1\" for format pattern %d", "%d", "1"); checkFormatPositionalFails("unsupported format character \".\" at index 1 in \"%.3g\"", "%.3g"); checkFormatPositionalFails("unsupported format character \".\" at index 1 in \"%.3g\"", "%.3g", 1, 2); checkFormatPositionalFails("unsupported format character \".\" at index 1 in \"%.s\"", "%.s"); } @Test public void testSingleQuotes() throws Exception { assertThat(Printer.str("test", '\'')).isEqualTo("test"); assertThat(Printer.repr("test", '\'')).isEqualTo("'test'"); assertEquals("'\\''", Printer.repr("'", '\'')); assertEquals("\"", Printer.str("\"", '\'')); assertEquals("'\"'", Printer.repr("\"", '\'')); List<?> list = MutableList.of(null, "foo", "bar"); List<?> tuple = Tuple.of("foo", "bar"); assertThat(Printer.str(Tuple.of(1, list, 3), '\'')).isEqualTo("(1, ['foo', 'bar'], 3)"); assertThat(Printer.repr(Tuple.of(1, list, 3), '\'')).isEqualTo("(1, ['foo', 'bar'], 3)"); assertThat(Printer.str(MutableList.of(null, 1, tuple, 3), '\'')) .isEqualTo("[1, ('foo', 'bar'), 3]"); assertThat(Printer.repr(MutableList.of(null, 1, tuple, 3), '\'')) .isEqualTo("[1, ('foo', 'bar'), 3]"); Map<Object, Object> dict = ImmutableMap.<Object, Object>of(1, tuple, 2, list, "foo", MutableList.of(null)); assertThat(Printer.str(dict, '\'')) .isEqualTo("{1: ('foo', 'bar'), 2: ['foo', 'bar'], 'foo': []}"); assertThat(Printer.repr(dict, '\'')) .isEqualTo("{1: ('foo', 'bar'), 2: ['foo', 'bar'], 'foo': []}"); } @Test public void testListLimitStringLength() throws Exception { int lengthDivisibleByTwo = Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH; if (lengthDivisibleByTwo % 2 == 1) { ++lengthDivisibleByTwo; } String limit = Strings.repeat("x", lengthDivisibleByTwo); String half = Strings.repeat("x", lengthDivisibleByTwo / 2); List<String> list = Arrays.asList(limit + limit); // String is way too long -> shorten. assertThat(printListWithLimit(list)).isEqualTo("[\"" + limit + "...\"]"); LinkedList<List<String>> nestedList = new LinkedList<>(); nestedList.add(list); // Same as above, but with one additional level of indirection. assertThat(printListWithLimit(nestedList)).isEqualTo("[[\"" + limit + "...\"]]"); // The inner list alone would meet the limit, but because of the first element, it has to be // shortened. assertThat(printListWithLimit(Arrays.asList(half, Arrays.asList(limit)))) .isEqualTo("[\"" + half + "\", [\"" + half + "...\"]]"); // String is too long, but the ellipsis make it even longer. assertThat(printListWithLimit(Arrays.asList(limit + "x"))).isEqualTo("[\"" + limit + "...\"]"); // We hit the limit exactly -> everything is printed. assertThat(printListWithLimit(Arrays.asList(limit))).isEqualTo("[\"" + limit + "\"]"); // Exact hit, but with two arguments -> everything is printed. assertThat(printListWithLimit(Arrays.asList(half, half))) .isEqualTo("[\"" + half + "\", \"" + half + "\"]"); // First argument hits the limit -> remaining argument is shortened. assertThat(printListWithLimit(Arrays.asList(limit, limit))) .isEqualTo("[\"" + limit + "\", \"...\"]"); String limitMinusOne = limit.substring(0, limit.length() - 1); // First arguments is one below the limit -> print first character of remaining argument. assertThat(printListWithLimit(Arrays.asList(limitMinusOne, limit))) .isEqualTo("[\"" + limitMinusOne + "\", \"x...\"]"); // First argument hits the limit -> we skip the remaining two arguments. assertThat(printListWithLimit(Arrays.asList(limit, limit, limit))) .isEqualTo("[\"" + limit + "\", <2 more arguments>]"); } @Test public void testListLimitTooManyArgs() throws Exception { StringBuilder builder = new StringBuilder(); List<Integer> maxLength = new LinkedList<>(); int next; for (next = 0; next < Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT; ++next) { maxLength.add(next); if (next > 0) { builder.append(", "); } builder.append(next); } // There is one too many, but we print every argument nonetheless. maxLength.add(next); assertThat(printListWithLimit(maxLength)).isEqualTo("[" + builder + ", " + next + "]"); // There are two too many, hence we don't print them. ++next; maxLength.add(next); assertThat(printListWithLimit(maxLength)).isEqualTo("[" + builder + ", <2 more arguments>]"); } @Test public void testPrintListDefaultNoLimit() throws Exception { List<Integer> list = new LinkedList<>(); // Make sure that the resulting string is longer than the suggestion. This should also lead to // way more items than suggested. for (int i = 0; i < Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH * 2; ++i) { list.add(i); } assertThat(Printer.str(list)).isEqualTo(String.format("[%s]", Joiner.on(", ").join(list))); } private String printListWithLimit(List<?> list) { return printList(list, Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT, Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH); } private String printList(List<?> list, int criticalElementsCount, int criticalStringLength) { StringBuilder builder = new StringBuilder(); Printer.printList( builder, list, "[", ", ", "]", "", '"', criticalElementsCount, criticalStringLength); return builder.toString(); } }