Home | History | Annotate | Download | only in command
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tradefed.command;
     18 
     19 import com.android.tradefed.command.CommandFileParser.CommandLine;
     20 import com.android.tradefed.config.ConfigurationException;
     21 import com.google.common.collect.ImmutableMap;
     22 import com.google.common.collect.ImmutableSet;
     23 
     24 import junit.framework.TestCase;
     25 
     26 import java.io.BufferedReader;
     27 import java.io.File;
     28 import java.io.IOException;
     29 import java.io.StringReader;
     30 import java.util.Arrays;
     31 import java.util.List;
     32 import java.util.Map;
     33 import java.util.Set;
     34 
     35 /**
     36  * Unit tests for {@link CommandFileParser}
     37  */
     38 public class CommandFileParserTest extends TestCase {
     39     /**
     40      * Uses a <File, String> map to provide {@link CommandFileParser} with mock data
     41      * for the mapped Files.
     42      */
     43     private static class MockCommandFileParser extends CommandFileParser {
     44         private final Map<File, String> mDataMap;
     45 
     46         MockCommandFileParser(Map<File, String> dataMap) {
     47             this.mDataMap = dataMap;
     48         }
     49 
     50         @Override
     51         BufferedReader createCommandFileReader(File file) {
     52             return new BufferedReader(new StringReader(mDataMap.get(file)));
     53         }
     54     }
     55 
     56     /** the {@link CommandFileParser} under test, with all dependencies mocked out */
     57     private CommandFileParser mCommandFile;
     58     private String mMockFileData = "";
     59     private static final File MOCK_FILE_PATH = new File("/path/to/");
     60     private File mMockFile = new File(MOCK_FILE_PATH, "original.txt");
     61 
     62     @Override
     63     protected void setUp() throws Exception {
     64         super.setUp();
     65         mCommandFile = new CommandFileParser() {
     66             @Override
     67             BufferedReader createCommandFileReader(File file) {
     68                return new BufferedReader(new StringReader(mMockFileData));
     69             }
     70         };
     71     }
     72 
     73     /**
     74      * Test parsing a command file with a comment and a single config.
     75      */
     76     public void testParse_singleConfig() throws Exception {
     77         // inject mock file data
     78         mMockFileData = "  #Comment followed by blank line\n \n--foo  config";
     79         List<String> expectedArgs = Arrays.asList("--foo", "config");
     80 
     81         assertParsedData(expectedArgs);
     82     }
     83 
     84     @SafeVarargs
     85     private final void assertParsedData(List<String>... expectedCommands) throws IOException,
     86             ConfigurationException {
     87         assertParsedData(mCommandFile, mMockFile, expectedCommands);
     88     }
     89 
     90     @SafeVarargs
     91     private final void assertParsedData(CommandFileParser parser, List<String>... expectedCommands)
     92             throws IOException, ConfigurationException {
     93         assertParsedData(parser, mMockFile, expectedCommands);
     94     }
     95 
     96     @SafeVarargs
     97     private final void assertParsedData(CommandFileParser parser, File file,
     98             List<String>... expectedCommands) throws IOException, ConfigurationException {
     99         List<CommandLine> data = parser.parseFile(file);
    100         assertEquals(expectedCommands.length, data.size());
    101 
    102         for (int i = 0; i < expectedCommands.length; i++) {
    103             assertEquals(expectedCommands[i].size(), data.get(i).size());
    104 
    105             for(int j = 0; j < expectedCommands[i].size(); j++) {
    106                 assertEquals(expectedCommands[i].get(j), data.get(i).get(j));
    107             }
    108         }
    109     }
    110 
    111     /**
    112      * Make sure that a config with a quoted argument is parsed properly.
    113      * <p/>
    114      * Whitespace inside of the quoted section should be preserved. Also, embedded escaped quotation
    115      * marks should be ignored.
    116      */
    117     public void testParseFile_quotedConfig() throws IOException, ConfigurationException  {
    118         // inject mock file data
    119         mMockFileData = "--foo \"this is a config\" --bar \"escap\\\\ed \\\" quotation\"";
    120         List<String> expectedArgs = Arrays.asList(
    121                 "--foo", "this is a config", "--bar", "escap\\\\ed \\\" quotation"
    122         );
    123 
    124         assertParsedData(expectedArgs);
    125     }
    126 
    127     /**
    128      * Test the data where the configuration ends inside a quotation.
    129      */
    130     public void testParseFile_endOnQuote() throws IOException {
    131         // inject mock file data
    132         mMockFileData = "--foo \"this is truncated";
    133 
    134         try {
    135             mCommandFile.parseFile(mMockFile);
    136             fail("ConfigurationException not thrown");
    137         } catch (ConfigurationException e) {
    138             // expected
    139         }
    140     }
    141 
    142     /**
    143      * Test the scenario where the configuration ends inside a quotation.
    144      */
    145     public void testRun_endWithEscape() throws IOException {
    146         // inject mock file data
    147         mMockFileData = "--foo escape\\";
    148         try {
    149             mCommandFile.parseFile(mMockFile);
    150             fail("ConfigurationException not thrown");
    151         } catch (ConfigurationException e) {
    152             // expected
    153         }
    154     }
    155 
    156     // Macro-related tests
    157     public void testSimpleMacro() throws IOException, ConfigurationException {
    158         mMockFileData = "MACRO TeSt = verify\nTeSt()";
    159         List<String> expectedArgs = Arrays.asList("verify");
    160 
    161         assertParsedData(expectedArgs);
    162     }
    163 
    164     /**
    165      * Ensure that when a macro is overwritten, the most recent value should be used.
    166      * <p />
    167      * Note that a message should also be logged; no good way to verify that currently
    168      */
    169     public void testOverwriteMacro() throws IOException, ConfigurationException {
    170         mMockFileData = "MACRO test = value 1\nMACRO test = value 2\ntest()";
    171         List<String> expectedArgs = Arrays.asList("value", "2");
    172 
    173         assertParsedData(expectedArgs);
    174     }
    175 
    176     /**
    177      * Ensure that parsing of quoted tokens continues to work
    178      */
    179     public void testSimpleMacro_quotedTokens() throws IOException, ConfigurationException {
    180         mMockFileData = "MACRO test = \"verify varify vorify\"\ntest()";
    181         List<String> expectedArgs = Arrays.asList("verify varify vorify");
    182 
    183         assertParsedData(expectedArgs);
    184     }
    185 
    186     /**
    187      * Ensure that parsing of names with embedded underscores works properly.
    188      */
    189     public void testSimpleMacro_underscoreName() throws IOException, ConfigurationException {
    190         mMockFileData = "MACRO under_score = verify\nunder_score()";
    191         List<String> expectedArgs = Arrays.asList("verify");
    192 
    193         assertParsedData(expectedArgs);
    194     }
    195 
    196     /**
    197      * Ensure that parsing of names with embedded hyphens works properly.
    198      */
    199     public void testSimpleMacro_hyphenName() throws IOException, ConfigurationException {
    200         mMockFileData = "MACRO hyphen-nated = verify\nhyphen-nated()";
    201         List<String> expectedArgs = Arrays.asList("verify");
    202 
    203         assertParsedData(expectedArgs);
    204     }
    205 
    206     /**
    207      * Test the scenario where a macro call doesn't resolve.
    208      */
    209     public void testUndefinedMacro() throws IOException {
    210         mMockFileData = "test()";
    211         try {
    212             mCommandFile.parseFile(mMockFile);
    213             fail("ConfigurationException not thrown");
    214         } catch (ConfigurationException e) {
    215             // expected
    216         }
    217     }
    218 
    219     /**
    220      * Test the scenario where a syntax problem causes a macro call to not resolve.
    221      */
    222     public void testUndefinedMacro_defSyntaxError() throws IOException {
    223         mMockFileData = "MACRO test = \n" +
    224                 "test()";
    225         try {
    226             mCommandFile.parseFile(mMockFile);
    227             fail("ConfigurationException not thrown");
    228         } catch (ConfigurationException e) {
    229             // expected
    230         }
    231     }
    232 
    233     /**
    234      * Simple test for LONG MACRO parsing
    235      */
    236     public void testSimpleLongMacro() throws IOException, ConfigurationException {
    237         mMockFileData = "LONG MACRO test\n" +
    238                 "verify\n" +
    239                 "END MACRO\n" +
    240                 "test()";
    241         List<String> expectedArgs = Arrays.asList("verify");
    242 
    243         assertParsedData(expectedArgs);
    244     }
    245 
    246     /**
    247      * Ensure that when a long macro is overwritten, the most recent value should be used.
    248      * <p />
    249      * Note that a message should also be logged; no good way to verify that currently
    250      */
    251 
    252 
    253     /**
    254      * Simple test for LONG MACRO parsing with multi-line expansion
    255      */
    256     public void testSimpleLongMacro_multiline() throws IOException, ConfigurationException {
    257         mMockFileData = "LONG MACRO test\n" +
    258                 "one two three\n" +
    259                 "a b c\n" +
    260                 "do re mi\n" +
    261                 "END MACRO\n" +
    262                 "test()";
    263         List<String>  expectedArgs1 = Arrays.asList("one", "two", "three");
    264         List<String>  expectedArgs2 = Arrays.asList("a", "b", "c");
    265         List<String>  expectedArgs3 = Arrays.asList("do", "re", "mi");
    266         assertParsedData(expectedArgs1, expectedArgs2, expectedArgs3);
    267 
    268     }
    269 
    270     /**
    271      * Simple test for LONG MACRO parsing with multi-line expansion
    272      */
    273     public void testLongMacro_withComment() throws IOException, ConfigurationException {
    274         mMockFileData = "LONG MACRO test\n" +
    275                 "\n" +  // blank line
    276                 "one two three\n" +
    277                 "#a b c\n" +
    278                 "do re mi\n" +
    279                 "END MACRO\n" +
    280                 "test()";
    281         List<String>  expectedArgs1 = Arrays.asList("one", "two", "three");
    282         List<String>  expectedArgs2 = Arrays.asList("do", "re", "mi");
    283         assertParsedData(expectedArgs1, expectedArgs2);
    284     }
    285 
    286     /**
    287      * Test the scenario where the configuration ends inside of a LONG MACRO definition.
    288      */
    289     public void testLongMacroSyntaxError_eof() throws IOException {
    290         mMockFileData = "LONG MACRO test\n" +
    291                 "verify\n" +
    292                 // "END MACRO\n" (this is the syntax error)
    293                 "test()";
    294 
    295         try {
    296             mCommandFile.parseFile(mMockFile);
    297             fail("ConfigurationException not thrown");
    298         } catch (ConfigurationException e) {
    299             // expected
    300         }
    301     }
    302 
    303     /**
    304      * Verifies that the line location data in CommandLine (file, line number) is accurate.
    305      */
    306     public void testCommandLineLocation() throws IOException, ConfigurationException {
    307         mMockFileData = "# This is a comment\n" +
    308                 "# This is another comment\n" +
    309                 "this --is-a-cmd\n" +
    310                 "one --final-cmd\n" +
    311                 "# More comments\n" +
    312                 "two --final-command";
    313 
    314         Set<CommandLine> expectedSet = ImmutableSet.<CommandLine>builder()
    315                 .add(new CommandLine(Arrays.asList("this", "--is-a-cmd"), mMockFile, 3))
    316                 .add(new CommandLine(Arrays.asList("one", "--final-cmd"), mMockFile, 4))
    317                 .add(new CommandLine(Arrays.asList("two", "--final-command"), mMockFile, 6))
    318                 .build();
    319 
    320 
    321         Set<CommandLine> parsedSet = ImmutableSet.<CommandLine>builder()
    322                 .addAll(mCommandFile.parseFile(mMockFile))
    323                 .build();
    324 
    325         assertEquals(expectedSet, parsedSet);
    326     }
    327 
    328     /**
    329      * Verifies that the line location data in CommandLine (file, line number) is accurate for
    330      * commands defined in included files.
    331      */
    332     public void testCommandLineLocation_withInclude() throws IOException, ConfigurationException {
    333         final File mockFile = new File(MOCK_FILE_PATH, "file.txt");
    334         final String mockFileData = "# This is a comment\n" +
    335                 "# This is another comment\n" +
    336                 "INCLUDE include.txt\n" +
    337                 "this --is-a-cmd\n" +
    338                 "one --final-cmd";
    339 
    340         final File mockIncludedFile = new File(MOCK_FILE_PATH, "include.txt");
    341         final String mockIncludedFileData = "# This is a comment\n" +
    342                 "# This is another comment\n" +
    343                 "inc --is-a-cmd\n" +
    344                 "# More comments\n" +
    345                 "inc --final-cmd";
    346 
    347         Set<CommandLine> expectedSet = ImmutableSet.<CommandLine>builder()
    348                 .add(new CommandLine(Arrays.asList("this", "--is-a-cmd"), mockFile, 4))
    349                 .add(new CommandLine(Arrays.asList("one", "--final-cmd"), mockFile, 5))
    350                 .add(new CommandLine(Arrays.asList("inc", "--is-a-cmd"), mockIncludedFile, 3))
    351                 .add(new CommandLine(Arrays.asList("inc", "--final-cmd"), mockIncludedFile, 5))
    352                 .build();
    353 
    354 
    355         CommandFileParser commandFileParser = new MockCommandFileParser(
    356                 ImmutableMap.<File, String>builder()
    357                 .put(mockFile, mockFileData)
    358                 .put(mockIncludedFile, mockIncludedFileData)
    359                 .build());
    360 
    361         Set<CommandLine> parsedSet = ImmutableSet.<CommandLine>builder()
    362                 .addAll(commandFileParser.parseFile(mockFile))
    363                 .build();
    364 
    365         assertEquals(expectedSet, parsedSet);
    366     }
    367 
    368     /**
    369      * Verify that the INCLUDE directive is behaving properly
    370      */
    371     public void testMacroParserInclude() throws Exception {
    372         final String mockFileData = "INCLUDE somefile.txt\n";
    373         final String mockIncludedFileData = "--foo bar\n";
    374         List<String>  expectedArgs = Arrays.asList("--foo", "bar");
    375 
    376         CommandFileParser commandFile = new CommandFileParser() {
    377             private boolean showInclude = true;
    378             @Override
    379             BufferedReader createCommandFileReader(File file) {
    380                 if (showInclude) {
    381                     showInclude = false;
    382                     return new BufferedReader(new StringReader(mockFileData));
    383                 } else {
    384                     return new BufferedReader(new StringReader(mockIncludedFileData));
    385                 }
    386             }
    387         };
    388 
    389         assertParsedData(commandFile, expectedArgs);
    390         assertEquals(1, commandFile.getIncludedFiles().size());
    391         assertTrue(commandFile.getIncludedFiles().iterator().next().endsWith("somefile.txt"));
    392     }
    393 
    394     /**
    395      * Verify that the INCLUDE directive works when used for two files
    396      */
    397     public void testMacroParserInclude_twice() throws Exception {
    398         final String mockFileData = "INCLUDE somefile.txt\n" +
    399                 "INCLUDE otherfile.txt\n";
    400         final String mockIncludedFileData1 = "--foo bar\n";
    401         final String mockIncludedFileData2 = "--baz quux\n";
    402         List<String>  expectedArgs1 = Arrays.asList("--foo", "bar");
    403         List<String>  expectedArgs2 = Arrays.asList("--baz", "quux");
    404 
    405         CommandFileParser commandFile = new CommandFileParser() {
    406             private int phase = 0;
    407             @Override
    408             BufferedReader createCommandFileReader(File file) {
    409                 if(phase == 0) {
    410                     phase++;
    411                     return new BufferedReader(new StringReader(mockFileData));
    412                 } else if (phase == 1) {
    413                     phase++;
    414                     return new BufferedReader(new StringReader(mockIncludedFileData1));
    415                 } else {
    416                     return new BufferedReader(new StringReader(mockIncludedFileData2));
    417                 }
    418             }
    419         };
    420         assertParsedData(commandFile, expectedArgs1, expectedArgs2);
    421     }
    422 
    423     /**
    424      * Verify that a file is only ever included once, regardless of how many INCLUDE directives for
    425      * that file show up
    426      */
    427     public void testMacroParserInclude_repeat() throws Exception {
    428         final String mockFileData = "INCLUDE somefile.txt\n" +
    429                 "INCLUDE somefile.txt\n";
    430         final String mockIncludedFileData1 = "--foo bar\n";
    431         List<String>  expectedArgs1 = Arrays.asList("--foo", "bar");
    432 
    433         CommandFileParser commandFile = new CommandFileParser() {
    434             private int phase = 0;
    435             @Override
    436             BufferedReader createCommandFileReader(File file) {
    437                 if(phase == 0) {
    438                     phase++;
    439                     return new BufferedReader(new StringReader(mockFileData));
    440                 } else {
    441                     return new BufferedReader(new StringReader(mockIncludedFileData1));
    442                 }
    443             }
    444         };
    445         assertParsedData(commandFile, expectedArgs1);
    446     }
    447 
    448     /**
    449      * Verify that the path of the file referenced by an INCLUDE directive is considered relative to
    450      * the location of the referencing file.
    451      */
    452     public void testMacroParserInclude_parentDir() throws Exception {
    453         // When we pass an unqualified filename, expect it to be taken relative to mMockFile's
    454         // parent directory
    455         final String includeFileName = "somefile.txt";
    456         final File expectedFile = new File(MOCK_FILE_PATH, includeFileName);
    457 
    458         final String mockFileData = String.format("INCLUDE %s\n", includeFileName);
    459         final String mockIncludedFileData = "--foo bar\n";
    460         List<String>  expectedArgs = Arrays.asList("--foo", "bar");
    461 
    462         CommandFileParser commandFile = new CommandFileParser() {
    463             @Override
    464             BufferedReader createCommandFileReader(File file) {
    465                 if (mMockFile.equals(file)) {
    466                     return new BufferedReader(new StringReader(mockFileData));
    467                 } else if (expectedFile.equals(file)) {
    468                     return new BufferedReader(new StringReader(mockIncludedFileData));
    469                 } else {
    470                     fail(String.format("Received unexpected request for contents of file %s",
    471                             file));
    472                     // shouldn't actually reach here
    473                     throw new RuntimeException();
    474                 }
    475             }
    476         };
    477         assertParsedData(commandFile, expectedArgs);
    478     }
    479 
    480     /**
    481      * Verify that INCLUDEing an absolute path works, even if a parent directory is specified.
    482      * <p />
    483      * This verifies the fix for a bug that existed because {@code File("/tmp", "/usr/bin")} creates
    484      * the path {@code /tmp/usr/bin}, rather than {@code /usr/bin} (which might be expected since
    485      * the child is actually an absolute path on its own.
    486      */
    487     public void testMacroParserInclude_absoluteInclude() throws Exception {
    488         // When we pass an unqualified filename, expect it to be taken relative to mMockFile's
    489         // parent directory
    490         final String includeFileName = "/usr/bin/somefile.txt";
    491         final File expectedFile = new File(includeFileName);
    492 
    493         final String mockFileData = String.format("INCLUDE %s\n", includeFileName);
    494         final String mockIncludedFileData = "--foo bar\n";
    495         List<String>  expectedArgs = Arrays.asList("--foo", "bar");
    496 
    497         CommandFileParser commandFile = new CommandFileParser() {
    498             @Override
    499             BufferedReader createCommandFileReader(File file) {
    500                 if (mMockFile.equals(file)) {
    501                     return new BufferedReader(new StringReader(mockFileData));
    502                 } else if (expectedFile.equals(file)) {
    503                     return new BufferedReader(new StringReader(mockIncludedFileData));
    504                 } else {
    505                     fail(String.format("Received unexpected request for contents of file %s",
    506                             file));
    507                     // shouldn't actually reach here
    508                     throw new RuntimeException();
    509                 }
    510             }
    511         };
    512         assertParsedData(commandFile, expectedArgs);
    513     }
    514 
    515     /**
    516      * Verify that if the original file is relative to no directory (aka ./), that the referenced
    517      * file is also relative to no directory.
    518      */
    519     public void testMacroParserInclude_noParentDir() throws Exception {
    520         final File mockFile = new File("original.txt");
    521         final String includeFileName = "somefile.txt";
    522         final File expectedFile = new File(includeFileName);
    523 
    524         final String mockFileData = String.format("INCLUDE %s\n", includeFileName);
    525         final String mockIncludedFileData = "--foo bar\n";
    526         List<String>  expectedArgs = Arrays.asList("--foo", "bar");
    527 
    528         CommandFileParser commandFile = new CommandFileParser() {
    529             @Override
    530             BufferedReader createCommandFileReader(File file) {
    531                 if (mockFile.equals(file)) {
    532                     return new BufferedReader(new StringReader(mockFileData));
    533                 } else if (expectedFile.equals(file)) {
    534                     return new BufferedReader(new StringReader(mockIncludedFileData));
    535                 } else {
    536                     fail(String.format("Received unexpected request for contents of file %s",
    537                             file));
    538                     // shouldn't actually reach here
    539                     throw new RuntimeException();
    540                 }
    541             }
    542         };
    543         assertParsedData(commandFile, mockFile, expectedArgs);
    544         assertEquals(1, commandFile.getIncludedFiles().size());
    545         assertTrue(commandFile.getIncludedFiles().iterator().next().endsWith(includeFileName));
    546     }
    547 
    548     /**
    549      * A testcase to make sure that the internal bitmask and mLines stay in sync
    550      * <p>
    551      * This tickles a bug in the current implementation (before I fix it).  The problem is here,
    552      * where the inputBitmask is only set to false conditionally, but inputBitmaskCount is
    553      * decremented unconditionally:
    554      * <code>inputBitmask.set(inputIdx, sawMacro);
    555      * --inputBitmaskCount;</code>
    556      */
    557     public void testMacroParserSync_suffix() throws IOException, ConfigurationException {
    558         mMockFileData = "MACRO alpha = one beta()\n" +
    559                 "MACRO beta = two\n" +
    560                 "alpha()\n";
    561         List<String>  expectedArgs = Arrays.asList("one", "two");
    562         // When the bug manifests, the result is {"one", "alpha()"}
    563 
    564         assertParsedData(expectedArgs);
    565     }
    566 
    567     /**
    568      * A testcase to make sure that the internal bitmask and mLines stay in sync
    569      * <p>
    570      * This tests a case related to the _suffix test above.
    571      */
    572     public void testMacroParserSync_prefix() throws IOException, ConfigurationException {
    573         mMockFileData = "MACRO alpha = beta() two\n" +
    574                 "MACRO beta = one\n" +
    575                 "alpha()\n";
    576         List<String>  expectedArgs = Arrays.asList("one", "two");
    577 
    578         assertParsedData(expectedArgs);
    579     }
    580 
    581     /**
    582      * Another test to verify a bugfix.  Long Macro expansion can cause the inputBitmask and its
    583      * cached form, inputBitmaskCount, to get out of sync.
    584      * <p />
    585      * The bug is that the cached value isn't incremented when a long macro is expanded (which means
    586      * that it may not account for the extra lines that it needs to process).  This manifests as a
    587      * partially-completed long macro expansion.
    588      * <p />
    589      * In this test, when the bug manifests, the first Command to be added will be
    590      * {@code ["one", "hbar()", "z", "x"} instead of the correct {@code ["one", "quux", "z", "x"]}.
    591      */
    592     public void testLongMacroSync() throws IOException, ConfigurationException {
    593         mMockFileData =
    594                 "MACRO hbar = quux\n" +
    595                 "LONG MACRO bar\n" +
    596                 "hbar() z\n" +
    597                 "END MACRO\n" +
    598                 "LONG MACRO foo\n" +
    599                 "bar() x\n" +
    600                 "END MACRO\n" +
    601                 "LONG MACRO test\n" +
    602                 "one foo()\n" +
    603                 "END MACRO\n" +
    604                 "test()\n" +
    605                 "hbar()\n";
    606 
    607         List<String> expectedArgs1 = Arrays.asList("one", "quux", "z", "x");
    608         List<String> expectedArgs2 = Arrays.asList("quux");
    609 
    610         assertParsedData(expectedArgs1, expectedArgs2);
    611     }
    612 }
    613