| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package parser |
| |
| import ( |
| "fmt" |
| "mojom/mojom_tool/mojom" |
| "os" |
| "path/filepath" |
| "reflect" |
| "strings" |
| "testing" |
| ) |
| |
| // FakeFileProvider implements FileProvider. |
| type FakeFileProvider struct { |
| // This field records the names of the files whose contents were requested. |
| requestedFileNames []string |
| } |
| |
| // FakeFileProvider implements provideContents by recording the name of the file |
| // whose contents are being requested and then returning the empty string. |
| func (f *FakeFileProvider) provideContents(fileRef *FileReference) (contents string, fileReadError error) { |
| f.requestedFileNames = append(f.requestedFileNames, fileRef.specifiedPath) |
| return "", nil |
| } |
| |
| // FakeFileProvider implements findFile by always succeeding to find the |
| // file and reporting that the |absolutePath| is equal to the |specifiedPath| |
| // with the prefix "/a/b/c/" |
| func (f *FakeFileProvider) findFile(fileRef *FileReference) error { |
| fileRef.absolutePath = fmt.Sprintf("/a/b/c/%s", fileRef.specifiedPath) |
| return nil |
| } |
| |
| type NoOpParseInvoker int |
| |
| func (NoOpParseInvoker) invokeParse(parser *Parser) {} |
| |
| // FakeFileExtractor implements FileExtractor |
| type FakeFileExtractor struct { |
| // This field maps a file name to the set of import names that we want to |
| // simulate the given file is importing. |
| importNames map[string][]string |
| } |
| |
| func makeFakeFileExtractor() FakeFileExtractor { |
| extractor := FakeFileExtractor{} |
| extractor.importNames = make(map[string][]string) |
| return extractor |
| } |
| |
| // Invoke this method to specify that FakeFileExtractor should simulate the fact |
| // that the file named |fileName| imports the files specified by |imports|. |
| func (f *FakeFileExtractor) appendImportsToFile(fileName string, imports ...string) { |
| if imports != nil { |
| f.importNames[fileName] = append(f.importNames[fileName], imports...) |
| } |
| } |
| |
| // FakeFileExtractor implements extractMojomFile() by returning an instance |
| // of MojomFile that has had imports added to it according to the values |
| // in the map |importNames|. |
| func (f *FakeFileExtractor) extractMojomFile(parser *Parser) *mojom.MojomFile { |
| file := parser.GetMojomFile() |
| for _, importName := range f.importNames[file.CanonicalFileName] { |
| file.AddImport(mojom.NewImportedFile(importName, nil)) |
| } |
| return file |
| } |
| |
| // TestExpectedFilesParsed tests the logic in function ParseDriver.ParseFiles() |
| // for determining which files should be parsed. |
| func TestExpectedFilesParsed(t *testing.T) { |
| // Construct our fake objects. |
| fakeFileProvider := FakeFileProvider{} |
| fakeFileExtractor := makeFakeFileExtractor() |
| // Our fake file1 will import file3, file4, file5 |
| fakeFileExtractor.appendImportsToFile("/a/b/c/file1", "file3", "file4", "file5") |
| // Our fake file5 will import file1 and file6 |
| fakeFileExtractor.appendImportsToFile("/a/b/c/file5", "file1", "file6") |
| |
| // Construct the driver under test |
| driver := newDriver([]string{}, false, false, &fakeFileProvider, &fakeFileExtractor, NoOpParseInvoker(0)) |
| |
| // Invoke ParseFiles with file1, file2 and file3 as top-level files. |
| descriptor, err := driver.ParseFiles([]string{"file1", "file2", "file3"}) |
| if err != nil { |
| t.Errorf(err.Error()) |
| } |
| |
| // Check that the correct files had their contents requested in the expected order. |
| expectedFileRefs := []string{"file1", "file2", "file3", "file4", "file5", "file6"} |
| if !reflect.DeepEqual(expectedFileRefs, fakeFileProvider.requestedFileNames) { |
| t.Errorf("%v != %v", expectedFileRefs, fakeFileProvider.requestedFileNames) |
| } |
| |
| // Retrieve the MojomFile for file1. |
| file1, ok := descriptor.MojomFilesByName["/a/b/c/file1"] |
| if !ok { |
| t.Fatalf("file1 not found.") |
| } |
| |
| // Check that it has 3 imports: file3, file4, file5 |
| if file1.Imports == nil { |
| t.Fatalf("file1 has no imports.") |
| } |
| if len(file1.Imports) != 3 { |
| t.Fatalf("len(file1.Imports)=%d", len(file1.Imports)) |
| } |
| if file1.Imports[0].CanonicalFileName != "/a/b/c/file3" { |
| t.Errorf("file1.Imports[0].CanonicalFileName=%q", file1.Imports[0].CanonicalFileName) |
| } |
| if file1.Imports[1].CanonicalFileName != "/a/b/c/file4" { |
| t.Errorf("file1.Imports[1].CanonicalFileName=%q", file1.Imports[1].CanonicalFileName) |
| } |
| if file1.Imports[2].CanonicalFileName != "/a/b/c/file5" { |
| t.Errorf("file1.Imports[1].CanonicalFileName=%q", file1.Imports[2].CanonicalFileName) |
| } |
| |
| // Retrieve the MojomFile for file3 |
| file3, ok := descriptor.MojomFilesByName["/a/b/c/file3"] |
| if !ok { |
| t.Fatalf("file3 not found.") |
| } |
| |
| // We expect the |ImportedFrom| field to be nil and the |SpecifiedFileName| |
| // to be non-empty because, even though file3 |
| // was imported from file1, it was also a top-level file. |
| if file3.ImportedFrom != nil { |
| t.Errorf("file3.ImportedFrom == %v", file3.ImportedFrom) |
| } |
| if file3.SpecifiedFileName != "file3" { |
| t.Errorf("file3.SpecifiedFileName == %v", file3.SpecifiedFileName) |
| } |
| |
| // Retrieve the MojomFile for file4 |
| file4, ok := descriptor.MojomFilesByName["/a/b/c/file4"] |
| if !ok { |
| t.Fatalf("file4 not found.") |
| } |
| |
| // We expect the |ImportedFrom| field to refer to file1 because file4 was imported |
| // from file1 and was not a top-level file. We expect |SpecifiedFileName| to |
| // be empty. |
| if file4.ImportedFrom != file1 { |
| t.Errorf("file4.ImportedFrom = %v", file4.ImportedFrom) |
| } |
| if file4.SpecifiedFileName != "" { |
| t.Errorf("file4.SpecifiedFileName = %v", file4.SpecifiedFileName) |
| } |
| |
| } |
| |
| // TestMetaDataOnlyMode tests the logic in function ParseDriver.ParseFiles() |
| // when metaDataOnlyMode is true. Imported files should not be parsed |
| // and CanonicalFileNames should not be set. |
| func TestMetaDataOnlyMode(t *testing.T) { |
| // Construct our fake objects. |
| fakeFileProvider := FakeFileProvider{} |
| fakeFileExtractor := makeFakeFileExtractor() |
| // Our fake file1 will import file3, file4, file5 |
| fakeFileExtractor.appendImportsToFile("/a/b/c/file1", "file3", "file4", "file5") |
| // Our fake file5 will import file1 and file6 |
| fakeFileExtractor.appendImportsToFile("/a/b/c/file5", "file1", "file6") |
| |
| // Construct the driver under test |
| driver := newDriver([]string{}, false, true, &fakeFileProvider, &fakeFileExtractor, NoOpParseInvoker(0)) |
| |
| // Invoke ParseFiles with file1, file2 and file3 as top-level files. |
| descriptor, err := driver.ParseFiles([]string{"file1", "file2", "file3"}) |
| if err != nil { |
| t.Errorf(err.Error()) |
| } |
| |
| // Check that the correct files had their contents requested in the expected order. |
| expectedFileRefs := []string{"file1", "file2", "file3"} |
| if !reflect.DeepEqual(expectedFileRefs, fakeFileProvider.requestedFileNames) { |
| t.Errorf("%v != %v", expectedFileRefs, fakeFileProvider.requestedFileNames) |
| } |
| |
| // Retrieve the MojomFile for file1. |
| file1, ok := descriptor.MojomFilesByName["/a/b/c/file1"] |
| if !ok { |
| t.Fatalf("file1 not found.") |
| } |
| |
| // Check that it has 3 imports: file3, file4, file5 |
| if file1.Imports == nil { |
| t.Fatalf("file1 has no imports.") |
| } |
| if len(file1.Imports) != 3 { |
| t.Fatalf("len(file1.Imports)=%d", len(file1.Imports)) |
| } |
| if file1.Imports[0].CanonicalFileName != "" { |
| t.Errorf("file1.Imports[0].CanonicalFileName=%q", file1.Imports[0].CanonicalFileName) |
| } |
| if file1.Imports[1].CanonicalFileName != "" { |
| t.Errorf("file1.Imports[1].CanonicalFileName=%q", file1.Imports[1].CanonicalFileName) |
| } |
| if file1.Imports[2].CanonicalFileName != "" { |
| t.Errorf("file1.Imports[1].CanonicalFileName=%q", file1.Imports[2].CanonicalFileName) |
| } |
| |
| // Retrieve the MojomFile for file3 |
| file3, ok := descriptor.MojomFilesByName["/a/b/c/file3"] |
| if !ok { |
| t.Fatalf("file3 not found.") |
| } |
| |
| // We expect the |ImportedFrom| field to be nil and the |SpecifiedFileName| |
| // to be non-empty because, even though file3 |
| // was imported from file1, it was also a top-level file. |
| if file3.ImportedFrom != nil { |
| t.Errorf("file3.ImportedFrom == %v", file3.ImportedFrom) |
| } |
| if file3.SpecifiedFileName != "file3" { |
| t.Errorf("file3.SpecifiedFileName == %v", file3.SpecifiedFileName) |
| } |
| |
| // Try to retrieve the MojomFile for file4. It should not be there. |
| if _, ok = descriptor.MojomFilesByName["/a/b/c/file4"]; ok { |
| t.Fatalf("file4 was unexpectedly found.") |
| } |
| } |
| |
| // TestOSFileProvider tests the function OSFileProvider.findFiles() and OSFileProvider.provideContents() |
| func TestOSFileProvider(t *testing.T) { |
| // Top level files (not imported from anything) are found relative to the current directory. |
| fileReferenceA := doOSFileProviderTest(t, "../test_data/a/testfile1", nil, nil, "mojom_tool/test_data/a") |
| fileReferenceB := doOSFileProviderTest(t, "../test_data/b/testfile1", nil, nil, "mojom_tool/test_data/b") |
| |
| // A file imported from a file in directory 'a' should be found in directory 'a' |
| doOSFileProviderTest(t, "testfile1", &fileReferenceA, nil, "mojom_tool/test_data/a") |
| |
| // A file imported from a file in directory 'b' should be found in directory 'a' |
| doOSFileProviderTest(t, "testfile1", &fileReferenceB, nil, "mojom_tool/test_data/b") |
| |
| // A file imported from a file in directory 'a' should be found in directory 'b' if there is no file with the |
| // specified name in directory 'a' and directory 'b' is in the search path. |
| doOSFileProviderTest(t, "testfile2", &fileReferenceA, []string{"../test_data/b"}, "mojom_tool/test_data/b") |
| |
| // The file is imported from directory 'a' and directory 'b' is on the search path but there is no file with the |
| // specified name in either directory and there is a file with the specified name in the current directory. |
| // The file should be found in the current directory. Note that the last argument is a random string of digits that |
| // we expect to find in the contents of the file parser_driver_test.go (i.e this file). |
| doOSFileProviderTest(t, "parse_driver_test.go", &fileReferenceA, []string{"../test_data/b"}, "840274941330987490326243") |
| } |
| |
| // doOSFileProviderTest is the workhorse for TestOSFileProvider. |
| func doOSFileProviderTest(t *testing.T, specifiedPath string, importedFrom *FileReference, |
| globalImports []string, expectedContents string) FileReference { |
| fileProvider := new(OSFileProvider) |
| fileProvider.importDirs = globalImports |
| fileReference := FileReference{specifiedPath: specifiedPath} |
| fileReference.importedFrom = importedFrom |
| |
| // Invoke findFile() |
| if err := fileProvider.findFile(&fileReference); err != nil { |
| t.Fatalf(err.Error()) |
| } |
| |
| // Check that the absolutePath has been populated to the absolute path of a file. |
| if fileReference.absolutePath == "" { |
| t.Fatalf("absolutePath is not set for %s", fileReference.specifiedPath) |
| } |
| if !filepath.IsAbs(fileReference.absolutePath) { |
| t.Fatalf("absolutePath is not absolute for %s: %s", fileReference.specifiedPath, fileReference.absolutePath) |
| } |
| info, err := os.Stat(fileReference.absolutePath) |
| if err != nil { |
| t.Fatalf("cannot stat absolutePath %s: %s", fileReference.absolutePath, err.Error()) |
| } |
| if info.IsDir() { |
| t.Fatalf("absolutePath refers to a directory: %s", fileReference.absolutePath) |
| } |
| |
| // Check that the dirPath has been populated to the absolute path of a directory. |
| if fileReference.directoryPath == "" { |
| t.Fatalf("directoryPath is not set for %s", fileReference.specifiedPath) |
| } |
| if !filepath.IsAbs(fileReference.directoryPath) { |
| t.Fatalf("directoryPath is not absolute for %s: %s", fileReference.specifiedPath, fileReference.directoryPath) |
| } |
| info, err = os.Stat(fileReference.directoryPath) |
| if err != nil { |
| t.Fatalf("cannot stat directoryPath %s: %s", fileReference.directoryPath, err.Error()) |
| } |
| if !info.IsDir() { |
| t.Fatalf("directoryPath does not refer to a directory: %s", fileReference.directoryPath) |
| } |
| if filepath.Dir(fileReference.absolutePath) != fileReference.directoryPath { |
| t.Fatalf("wrong directoryPath expected parent of %s got %s", fileReference.absolutePath, fileReference.directoryPath) |
| } |
| |
| contents, err := fileProvider.provideContents(&fileReference) |
| if err != nil { |
| t.Errorf("Error from provideContents for %v: %s", fileReference, err.Error()) |
| } |
| if !strings.Contains(contents, expectedContents) { |
| t.Errorf("Wrong file contents for %v. Expecting %s got %s.", fileReference, expectedContents, contents) |
| } |
| |
| return fileReference |
| } |