| // Copyright 2022 Google LLC |
| // |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package exporter |
| |
| import ( |
| "bytes" |
| "path/filepath" |
| "testing" |
| |
| "github.com/stretchr/testify/assert" |
| "github.com/stretchr/testify/mock" |
| "github.com/stretchr/testify/require" |
| "go.skia.org/skia/bazel/exporter/build_proto/build" |
| "go.skia.org/skia/bazel/exporter/interfaces/mocks" |
| "google.golang.org/protobuf/proto" |
| ) |
| |
| // The expected gn/core.gni file contents for createCoreSourcesQueryResult(). |
| // This expected result is handmade. |
| const publicSrcsExpectedGNI = `# DO NOT EDIT: This is a generated file. |
| # See //bazel/exporter_tool/README.md for more information. |
| # |
| # The sources of truth are: |
| # //src/core/BUILD.bazel |
| # //src/opts/BUILD.bazel |
| |
| # To update this file, run make -C bazel generate_gni |
| |
| _src = get_path_info("../src", "abspath") |
| |
| # List generated by Bazel rules: |
| # //src/core:core_srcs |
| # //src/opts:private_hdrs |
| skia_core_sources = [ |
| "$_src/core/SkAAClip.cpp", |
| "$_src/core/SkATrace.cpp", |
| "$_src/core/SkAlphaRuns.cpp", |
| "$_src/opts/SkBitmapProcState_opts.h", |
| "$_src/opts/SkBlitMask_opts.h", |
| "$_src/opts/SkBlitRow_opts.h", |
| ] |
| |
| skia_core_sources += skia_pathops_sources |
| |
| skia_core_public += skia_pathops_public |
| |
| ` |
| |
| var exportDescs = []GNIExportDesc{ |
| {GNI: "gn/core.gni", Vars: []GNIFileListExportDesc{ |
| {Var: "skia_core_sources", |
| Rules: []string{ |
| "//src/core:core_srcs", |
| "//src/opts:private_hdrs", |
| }}}, |
| }, |
| } |
| |
| var testExporterParams = GNIExporterParams{ |
| WorkspaceDir: "/path/to/workspace", |
| ExportDescs: exportDescs, |
| } |
| |
| func createCoreSourcesQueryResult() *build.QueryResult { |
| qr := build.QueryResult{} |
| ruleDesc := build.Target_RULE |
| |
| // Rule #1 |
| srcs := []string{ |
| "//src/core:SkAAClip.cpp", |
| "//src/core:SkATrace.cpp", |
| "//src/core:SkAlphaRuns.cpp", |
| } |
| r1 := createTestBuildRule("//src/core:core_srcs", "filegroup", |
| "/path/to/workspace/src/core/BUILD.bazel:376:20", srcs) |
| t1 := build.Target{Rule: r1, Type: &ruleDesc} |
| qr.Target = append(qr.Target, &t1) |
| |
| // Rule #2 |
| srcs = []string{ |
| "//src/opts:SkBitmapProcState_opts.h", |
| "//src/opts:SkBlitMask_opts.h", |
| "//src/opts:SkBlitRow_opts.h", |
| } |
| r2 := createTestBuildRule("//src/opts:private_hdrs", "filegroup", |
| "/path/to/workspace/src/opts/BUILD.bazel:26:10", srcs) |
| t2 := build.Target{Rule: r2, Type: &ruleDesc} |
| qr.Target = append(qr.Target, &t2) |
| return &qr |
| } |
| |
| func TestGNIExporterExport_ValidInput_Success(t *testing.T) { |
| qr := createCoreSourcesQueryResult() |
| protoData, err := proto.Marshal(qr) |
| require.NoError(t, err) |
| |
| fs := mocks.NewFileSystem(t) |
| var contents bytes.Buffer |
| fs.On("OpenFile", mock.Anything).Once().Run(func(args mock.Arguments) { |
| path := args.String(0) |
| assert.True(t, filepath.IsAbs(path)) |
| assert.Equal(t, "/path/to/workspace/gn/core.gni", filepath.ToSlash(path)) |
| }).Return(&contents, nil).Once() |
| e := NewGNIExporter(testExporterParams, fs) |
| qcmd := mocks.NewQueryCommand(t) |
| qcmd.On("Read", mock.Anything).Return(protoData, nil).Once() |
| err = e.Export(qcmd) |
| require.NoError(t, err) |
| |
| assert.Equal(t, publicSrcsExpectedGNI, contents.String()) |
| } |
| |
| func TestMakeRelativeFilePathForGNI_MatchingRootDir_Success(t *testing.T) { |
| test := func(name, target, expectedPath string) { |
| t.Run(name, func(t *testing.T) { |
| path, err := makeRelativeFilePathForGNI(target) |
| require.NoError(t, err) |
| assert.Equal(t, expectedPath, path) |
| }) |
| } |
| |
| test("src", "src/core/file.cpp", "$_src/core/file.cpp") |
| test("include", "include/core/file.h", "$_include/core/file.h") |
| test("modules", "modules/mod/file.cpp", "$_modules/mod/file.cpp") |
| } |
| |
| func TestMakeRelativeFilePathForGNI_IndalidInput_ReturnError(t *testing.T) { |
| test := func(name, target string) { |
| t.Run(name, func(t *testing.T) { |
| _, err := makeRelativeFilePathForGNI(target) |
| assert.Error(t, err) |
| }) |
| } |
| |
| test("EmptyString", "") |
| test("UnsupportedRootDir", "//valid/rule/incorrect/root/dir:file.cpp") |
| } |
| |
| func TestIsHeaderFile_HeaderFiles_ReturnTrue(t *testing.T) { |
| test := func(name, path string) { |
| t.Run(name, func(t *testing.T) { |
| assert.True(t, isHeaderFile(path)) |
| }) |
| } |
| |
| test("LowerH", "path/to/file.h") |
| test("UpperH", "path/to/file.H") |
| test("MixedHpp", "path/to/file.Hpp") |
| } |
| |
| func TestIsHeaderFile_NonHeaderFiles_ReturnTrue(t *testing.T) { |
| test := func(name, path string) { |
| t.Run(name, func(t *testing.T) { |
| assert.False(t, isHeaderFile(path)) |
| }) |
| } |
| |
| test("EmptyString", "") |
| test("DirPath", "/path/to/dir") |
| test("C++Source", "/path/to/file.cpp") |
| test("DotHInDir", "/path/to/dir.h/file.cpp") |
| test("Go", "main.go") |
| } |
| |
| func TestFileListContainsOnlyCppHeaderFiles_AllHeaders_ReturnsTrue(t *testing.T) { |
| test := func(name string, paths []string) { |
| t.Run(name, func(t *testing.T) { |
| assert.True(t, fileListContainsOnlyCppHeaderFiles(paths)) |
| }) |
| } |
| |
| test("OneFile", []string{"file.h"}) |
| test("Multiple", []string{"file.h", "foo.hpp"}) |
| } |
| |
| func TestFileListContainsOnlyCppHeaderFiles_NotAllHeaders_ReturnsFalse(t *testing.T) { |
| test := func(name string, paths []string) { |
| t.Run(name, func(t *testing.T) { |
| assert.False(t, fileListContainsOnlyCppHeaderFiles(paths)) |
| }) |
| } |
| |
| test("Nil", nil) |
| test("HeaderFiles", []string{"file.h", "file2.cpp"}) |
| test("GoFile", []string{"file.go"}) |
| } |
| |
| func TestWorkspaceToAbsPath_ReturnsAbsolutePath(t *testing.T) { |
| fs := mocks.NewFileSystem(t) |
| e := NewGNIExporter(testExporterParams, fs) |
| require.NotNil(t, e) |
| |
| test := func(name, input, expected string) { |
| t.Run(name, func(t *testing.T) { |
| assert.Equal(t, expected, e.workspaceToAbsPath(input)) |
| }) |
| } |
| |
| test("FileInDir", "foo/bar.txt", "/path/to/workspace/foo/bar.txt") |
| test("DirInDir", "foo/bar", "/path/to/workspace/foo/bar") |
| test("RootFile", "root.txt", "/path/to/workspace/root.txt") |
| test("WorkspaceDir", "", "/path/to/workspace") |
| } |
| |
| func TestAbsToWorkspacePath_PathInWorkspace_ReturnsRelativePath(t *testing.T) { |
| fs := mocks.NewFileSystem(t) |
| e := NewGNIExporter(testExporterParams, fs) |
| require.NotNil(t, e) |
| |
| test := func(name, input, expected string) { |
| t.Run(name, func(t *testing.T) { |
| path, err := e.absToWorkspacePath(input) |
| assert.NoError(t, err) |
| assert.Equal(t, expected, path) |
| }) |
| } |
| |
| test("FileInDir", "/path/to/workspace/foo/bar.txt", "foo/bar.txt") |
| test("DirInDir", "/path/to/workspace/foo/bar", "foo/bar") |
| test("RootFile", "/path/to/workspace/root.txt", "root.txt") |
| test("RootFile", "/path/to/workspace/世界", "世界") |
| test("WorkspaceDir", "/path/to/workspace", "") |
| } |
| |
| func TestAbsToWorkspacePath_PathNotInWorkspace_ReturnsError(t *testing.T) { |
| fs := mocks.NewFileSystem(t) |
| e := NewGNIExporter(testExporterParams, fs) |
| require.NotNil(t, e) |
| |
| _, err := e.absToWorkspacePath("/path/to/file.txt") |
| assert.Error(t, err) |
| } |
| |
| func TestGetGNILineVariable_LinesWithVariables_ReturnVariable(t *testing.T) { |
| test := func(name, inputLine, expected string) { |
| t.Run(name, func(t *testing.T) { |
| assert.Equal(t, expected, getGNILineVariable(inputLine)) |
| }) |
| } |
| |
| test("EqualWithSpaces", `foo = [ "something" ]`, "foo") |
| test("EqualNoSpaces", `foo=[ "something" ]`, "foo") |
| test("EqualSpaceBefore", `foo =[ "something" ]`, "foo") |
| test("MultilineList", `foo = [`, "foo") |
| } |
| |
| func TestGetGNILineVariable_LinesWithVariables_NoMatch(t *testing.T) { |
| test := func(name, inputLine, expected string) { |
| t.Run(name, func(t *testing.T) { |
| assert.Equal(t, expected, getGNILineVariable(inputLine)) |
| }) |
| } |
| |
| test("FirstCharSpace", ` foo = [ "something" ]`, "") // Impl. requires formatted file. |
| test("NotList", `foo = bar`, "") |
| test("ListLiteral", `[ "something" ]`, "") |
| test("ListInComment", `# foo = [ "something" ]`, "") |
| test("MissingVariable", `=[ "something" ]`, "") |
| test("EmptyString", ``, "") |
| } |
| |
| func TestExtractTopLevelFolder_PathsWithTopDir_ReturnsTopDir(t *testing.T) { |
| test := func(name, input, expected string) { |
| t.Run(name, func(t *testing.T) { |
| assert.Equal(t, expected, extractTopLevelFolder(input)) |
| }) |
| } |
| test("TopIsDir", "foo/bar/baz.txt", "foo") |
| test("TopIsVariable", "$_src/bar/baz.txt", "$_src") |
| test("TopIsFile", "baz.txt", "baz.txt") |
| test("TopIsAbsDir", "/foo/bar/baz.txt", "") |
| } |
| |
| func TestExtractTopLevelFolder_PathsWithNoTopDir_ReturnsEmptyString(t *testing.T) { |
| test := func(name, input, expected string) { |
| t.Run(name, func(t *testing.T) { |
| assert.Equal(t, expected, extractTopLevelFolder(input)) |
| }) |
| } |
| test("EmptyString", "", "") |
| test("EmptyAbsRoot", "/", "") |
| test("MultipleSlashes", "///", "") |
| } |
| |
| func TestAddGNIVariablesToWorkspacePaths_ValidInput_ReturnsVariables(t *testing.T) { |
| test := func(name string, inputPaths, expected []string) { |
| t.Run(name, func(t *testing.T) { |
| gniPaths, err := addGNIVariablesToWorkspacePaths(inputPaths) |
| require.NoError(t, err) |
| assert.Equal(t, expected, gniPaths) |
| }) |
| } |
| test("EmptySlice", nil, []string{}) |
| test("AllVariables", |
| []string{"src/include/foo.h", |
| "include/foo.h", |
| "modules/foo.cpp"}, |
| []string{"$_src/include/foo.h", |
| "$_include/foo.h", |
| "$_modules/foo.cpp"}) |
| } |
| |
| func TestAddGNIVariablesToWorkspacePaths_InvalidInput_ReturnsError(t *testing.T) { |
| test := func(name string, inputPaths []string) { |
| t.Run(name, func(t *testing.T) { |
| _, err := addGNIVariablesToWorkspacePaths(inputPaths) |
| assert.Error(t, err) |
| }) |
| } |
| test("InvalidTopDir", []string{"nomatch/include/foo.h"}) |
| test("RuleNotPath", []string{"//src/core:source.cpp"}) |
| } |
| |
| func TestConvertTargetsToFilePaths_ValidInput_ReturnsPaths(t *testing.T) { |
| test := func(name string, inputTargets, expected []string) { |
| t.Run(name, func(t *testing.T) { |
| paths, err := convertTargetsToFilePaths(inputTargets) |
| require.NoError(t, err) |
| assert.Equal(t, expected, paths) |
| }) |
| } |
| test("EmptySlice", nil, []string{}) |
| test("Files", |
| []string{"//src/include:foo.h", |
| "//include:foo.h", |
| "//modules:foo.cpp"}, |
| []string{"src/include/foo.h", |
| "include/foo.h", |
| "modules/foo.cpp"}) |
| } |
| |
| func TestConvertTargetsToFilePaths_InvalidInput_ReturnsError(t *testing.T) { |
| test := func(name string, inputTargets []string) { |
| t.Run(name, func(t *testing.T) { |
| _, err := convertTargetsToFilePaths(inputTargets) |
| assert.Error(t, err) |
| }) |
| } |
| test("EmptyString", []string{""}) |
| test("ValidTargetEmptyString", []string{"//src/include:foo.h", ""}) |
| test("EmptyStringValidTarget", []string{"//src/include:foo.h", ""}) |
| } |
| |
| func TestRemoveDuplicate_ContainsDuplicates_SortedAndDuplicatesRemoved(t *testing.T) { |
| files := []string{ |
| "alpha", |
| "beta", |
| "gamma", |
| "delta", |
| "beta", |
| "Alpha", |
| "alpha", |
| "path/to/file", |
| "path/to/file2", |
| "path/to/file", |
| } |
| output := removeDuplicates(files) |
| assert.Equal(t, []string{ |
| "Alpha", |
| "alpha", |
| "beta", |
| "delta", |
| "gamma", |
| "path/to/file", |
| "path/to/file2", |
| }, output) |
| } |
| |
| func TestRemoveDuplicates_NoDuplicates_ReturnsOnlySorted(t *testing.T) { |
| files := []string{ |
| "Beta", |
| "ALPHA", |
| "gamma", |
| "path/to/file2", |
| "path/to/file", |
| } |
| output := removeDuplicates(files) |
| assert.Equal(t, []string{ |
| "ALPHA", |
| "Beta", |
| "gamma", |
| "path/to/file", |
| "path/to/file2", |
| }, output) |
| } |
| |
| func TestGetPathToTopDir_ValidRelativePaths_ReturnsExpected(t *testing.T) { |
| test := func(name, expected, input string) { |
| t.Run(name, func(t *testing.T) { |
| assert.Equal(t, expected, getPathToTopDir(input)) |
| }) |
| } |
| test("TopDir", ".", "core.gni") |
| test("OneDown", "..", "gn/core.gni") |
| test("TwoDown", "../..", "modules/skcms/skcms.gni") |
| } |
| |
| func TestGetPathToTopDir_AbsolutePath_ReturnsEmptyString(t *testing.T) { |
| // Exporter shouldn't use absolute paths, but just to be safe. |
| assert.Equal(t, "", getPathToTopDir("/")) |
| } |