• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MockCommandFileParser(Map<File, String> dataMap)46         MockCommandFileParser(Map<File, String> dataMap) {
47             this.mDataMap = dataMap;
48         }
49 
50         @Override
createCommandFileReader(File file)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
setUp()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      */
testParse_singleConfig()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
assertParsedData(List<String>.... expectedCommands)85     private final void assertParsedData(List<String>... expectedCommands) throws IOException,
86             ConfigurationException {
87         assertParsedData(mCommandFile, mMockFile, expectedCommands);
88     }
89 
90     @SafeVarargs
assertParsedData(CommandFileParser parser, List<String>... expectedCommands)91     private final void assertParsedData(CommandFileParser parser, List<String>... expectedCommands)
92             throws IOException, ConfigurationException {
93         assertParsedData(parser, mMockFile, expectedCommands);
94     }
95 
96     @SafeVarargs
assertParsedData(CommandFileParser parser, File file, List<String>... expectedCommands)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      */
testParseFile_quotedConfig()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      */
testParseFile_endOnQuote()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      */
testRun_endWithEscape()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
testSimpleMacro()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      */
testOverwriteMacro()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      */
testSimpleMacro_quotedTokens()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      */
testSimpleMacro_underscoreName()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      */
testSimpleMacro_hyphenName()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      */
testUndefinedMacro()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      */
testUndefinedMacro_defSyntaxError()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      */
testSimpleLongMacro()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      */
testSimpleLongMacro_multiline()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      */
testLongMacro_withComment()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      */
testLongMacroSyntaxError_eof()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      */
testCommandLineLocation()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      */
testCommandLineLocation_withInclude()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      */
testMacroParserInclude()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      */
testMacroParserInclude_twice()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      */
testMacroParserInclude_repeat()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      */
testMacroParserInclude_parentDir()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      */
testMacroParserInclude_absoluteInclude()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      */
testMacroParserInclude_noParentDir()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      */
testMacroParserSync_suffix()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      */
testMacroParserSync_prefix()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      */
testLongMacroSync()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