| // Copyright 2019 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.bazel.rules.ninja; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows; |
| |
| import com.google.common.collect.ImmutableSortedMap; |
| import com.google.common.collect.Range; |
| import com.google.devtools.build.lib.bazel.rules.ninja.file.ByteBufferFragment; |
| import com.google.devtools.build.lib.bazel.rules.ninja.file.GenericParsingException; |
| import com.google.devtools.build.lib.bazel.rules.ninja.lexer.NinjaLexer; |
| import com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaParser; |
| import com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaRule; |
| import com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaRuleVariable; |
| import com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaVariableValue; |
| import com.google.devtools.build.lib.collect.ImmutableSortedKeyListMultimap; |
| import com.google.devtools.build.lib.util.Pair; |
| import java.nio.ByteBuffer; |
| import java.nio.charset.StandardCharsets; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests for {@link com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaParser}. */ |
| @RunWith(JUnit4.class) |
| public class NinjaParserTest { |
| @Test |
| public void testSimpleVariable() throws Exception { |
| doTestSimpleVariable("a=b", "a", "b"); |
| doTestSimpleVariable("a=b\nc", "a", "b"); |
| doTestSimpleVariable("a=b # comment", "a", "b"); |
| doTestSimpleVariable("a.b.c = some long value", "a.b.c", "some long value"); |
| doTestSimpleVariable("a_11_24-rt.15= ^&%$#@", "a_11_24-rt.15", "^&%$"); |
| } |
| |
| @Test |
| public void testVariableParsingException() { |
| doTestVariableParsingException(" ", "Expected identifier, but got indent"); |
| doTestVariableParsingException("a", "Expected = after 'a'"); |
| doTestVariableParsingException("a=:", "Variable 'a' has no value."); |
| doTestVariableParsingException( |
| "^a=", "Expected identifier, but got error: 'Symbol is not allowed in the identifier.'"); |
| } |
| |
| private static void doTestVariableParsingException(String text, String message) { |
| GenericParsingException exception = |
| assertThrows(GenericParsingException.class, () -> createParser(text).parseVariable()); |
| assertThat(exception).hasMessageThat().isEqualTo(message); |
| } |
| |
| @Test |
| public void testNoValue() { |
| doTestNoValue("a="); |
| doTestNoValue("a=\u000018"); |
| doTestNoValue("a = "); |
| doTestNoValue("a =\nm"); |
| doTestNoValue("a = # 123"); |
| } |
| |
| @Test |
| public void testWithVariablesInValue() throws Exception { |
| doTestWithVariablesInValue("a=$a $b", "a", "$a $b", expectedVariables("a", 2, 4, "b", 5, 7)); |
| doTestWithVariablesInValue("a=a_$b_c", "a", "a_$b_c", expectedVariables("b_c", 4, 8)); |
| doTestWithVariablesInValue("a=$b a c", "a", "$b a c", expectedVariables("b", 2, 4)); |
| doTestWithVariablesInValue("a=a_$b c", "a", "a_$b c", expectedVariables("b", 4, 6)); |
| doTestWithVariablesInValue("a=a_${b.d}c", "a", "a_${b.d}c", expectedVariables("b.d", 4, 10)); |
| doTestWithVariablesInValue( |
| "e=a$b*c${ d }*18", "e", "a$b*c${ d }*18", expectedVariables("b", 3, 5, "d", 7, 13)); |
| doTestWithVariablesInValue( |
| "e=a$b*${ b }", "e", "a$b*${ b }", expectedVariables("b", 3, 5, "b", 6, 12)); |
| } |
| |
| @Test |
| public void testNormalizeVariableName() { |
| assertThat(NinjaParser.normalizeVariableName("$a")).isEqualTo("a"); |
| assertThat(NinjaParser.normalizeVariableName("$a-b-c")).isEqualTo("a-b-c"); |
| assertThat(NinjaParser.normalizeVariableName("${abc_de-7}")).isEqualTo("abc_de-7"); |
| assertThat(NinjaParser.normalizeVariableName("${ a1.5}")).isEqualTo("a1.5"); |
| assertThat(NinjaParser.normalizeVariableName("${a1.5 }")).isEqualTo("a1.5"); |
| } |
| |
| @Test |
| public void testInclude() throws Exception { |
| NinjaVariableValue value1 = createParser("include x/multi words/z").parseIncludeStatement(); |
| assertThat(value1.getText()).isEqualTo("x/multi words/z"); |
| |
| NinjaVariableValue value2 = createParser("subninja ${x}.ninja").parseSubNinjaStatement(); |
| assertThat(value2.getText()).isEqualTo("${x}.ninja"); |
| assertThat(value2.getVariables()).containsExactly("x", Range.openClosed(9, 13)); |
| } |
| |
| @Test |
| public void testIncludeErrors() { |
| GenericParsingException exception1 = |
| assertThrows( |
| GenericParsingException.class, |
| () -> createParser("include x :").parseIncludeStatement()); |
| assertThat(exception1).hasMessageThat().isEqualTo("Expected newline, but got :"); |
| |
| GenericParsingException exception2 = |
| assertThrows( |
| GenericParsingException.class, () -> createParser("include").parseIncludeStatement()); |
| assertThat(exception2).hasMessageThat().isEqualTo("include statement has no path."); |
| |
| GenericParsingException exception3 = |
| assertThrows( |
| GenericParsingException.class, |
| () -> createParser("subninja \nm").parseSubNinjaStatement()); |
| assertThat(exception3).hasMessageThat().isEqualTo("subninja statement has no path."); |
| } |
| |
| @Test |
| public void testNinjaRule() throws Exception { |
| NinjaParser parser = |
| createParser( |
| "rule testRule \n" |
| + " command = executable --flag $TARGET $out && $POST_BUILD\n" |
| + " description = Test rule for $TARGET\n" |
| + " rspfile = $TARGET.in\n" |
| + " deps = ${abc} $\n" |
| + " ${cde}\n"); |
| NinjaRule ninjaRule = parser.parseNinjaRule(); |
| ImmutableSortedMap<NinjaRuleVariable, NinjaVariableValue> variables = ninjaRule.getVariables(); |
| assertThat(variables.keySet()) |
| .containsExactly( |
| NinjaRuleVariable.NAME, |
| NinjaRuleVariable.COMMAND, |
| NinjaRuleVariable.DESCRIPTION, |
| NinjaRuleVariable.RSPFILE, |
| NinjaRuleVariable.DEPS); |
| assertThat(variables.get(NinjaRuleVariable.NAME).getText()).isEqualTo("testRule"); |
| assertThat(variables.get(NinjaRuleVariable.DEPS).getText()).isEqualTo("${abc} $\n ${cde}"); |
| } |
| |
| @Test |
| public void testNinjaRuleParsingException() { |
| doTestNinjaRuleParsingException( |
| "rule testRule extra-word\n", "Expected newline, but got identifier"); |
| doTestNinjaRuleParsingException( |
| "rule testRule\n command =", "Variable 'command' has no value."); |
| doTestNinjaRuleParsingException( |
| "rule testRule\ncommand =", "Expected indent, but got identifier"); |
| doTestNinjaRuleParsingException( |
| "rule testRule\n ^custom = a", |
| "Expected identifier, but got error: 'Symbol is not allowed in the identifier.'"); |
| doTestNinjaRuleParsingException("rule testRule\n custom = a", "Unexpected variable 'custom'"); |
| } |
| |
| private static void doTestNinjaRuleParsingException(String text, String message) { |
| GenericParsingException exception = |
| assertThrows(GenericParsingException.class, () -> createParser(text).parseNinjaRule()); |
| assertThat(exception).hasMessageThat().isEqualTo(message); |
| } |
| |
| private static void doTestSimpleVariable(String text, String name, String value) |
| throws GenericParsingException { |
| NinjaParser parser = createParser(text); |
| Pair<String, NinjaVariableValue> variable = parser.parseVariable(); |
| assertThat(variable.getFirst()).isEqualTo(name); |
| assertThat(variable.getSecond()).isNotNull(); |
| assertThat(variable.getSecond().getText()).isEqualTo(value); |
| assertThat(variable.getSecond().getVariables()).isEmpty(); |
| } |
| |
| private static void doTestNoValue(String text) { |
| NinjaParser parser = createParser(text); |
| GenericParsingException exception = |
| assertThrows(GenericParsingException.class, parser::parseVariable); |
| assertThat(exception).hasMessageThat().isEqualTo("Variable 'a' has no value."); |
| } |
| |
| private static ImmutableSortedKeyListMultimap<String, Range<Integer>> expectedVariables( |
| String name, int start, int end) { |
| return ImmutableSortedKeyListMultimap.<String, Range<Integer>>builder() |
| .put(name, Range.openClosed(start, end)) |
| .build(); |
| } |
| |
| private static ImmutableSortedKeyListMultimap<String, Range<Integer>> expectedVariables( |
| String name1, int start1, int end1, String name2, int start2, int end2) { |
| return ImmutableSortedKeyListMultimap.<String, Range<Integer>>builder() |
| .put(name1, Range.openClosed(start1, end1)) |
| .put(name2, Range.openClosed(start2, end2)) |
| .build(); |
| } |
| |
| private static void doTestWithVariablesInValue( |
| String text, |
| String name, |
| String value, |
| ImmutableSortedKeyListMultimap<String, Range<Integer>> expectedVars) |
| throws GenericParsingException { |
| NinjaParser parser = createParser(text); |
| Pair<String, NinjaVariableValue> variable = parser.parseVariable(); |
| assertThat(variable.getFirst()).isEqualTo(name); |
| assertThat(variable.getSecond()).isNotNull(); |
| assertThat(variable.getSecond().getText()).isEqualTo(value); |
| |
| ImmutableSortedKeyListMultimap<String, Range<Integer>> variables = |
| variable.getSecond().getVariables(); |
| assertThat(variables).containsExactlyEntriesIn(expectedVars); |
| } |
| |
| private static NinjaParser createParser(String text) { |
| ByteBuffer buffer = ByteBuffer.wrap(text.getBytes(StandardCharsets.ISO_8859_1)); |
| NinjaLexer lexer = new NinjaLexer(new ByteBufferFragment(buffer, 0, buffer.limit())); |
| return new NinjaParser(lexer); |
| } |
| } |