blob: c420416adc111b57816b5516a25aba3cc3aae5d7 [file] [log] [blame]
// 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_sources += skia_skpicture_sources
skia_core_public += skia_pathops_public
skia_core_public += skia_skpicture_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 TestFoo_DeprecatedFiles_ReturnsTrue(t *testing.T) {
assert.True(t, isSourceFileDeprecated("include/core/SkDrawLooper.h"))
}
func TestFoo_NotDeprecatedFiles_ReturnsFalse(t *testing.T) {
assert.False(t, isSourceFileDeprecated("include/core/SkColor.h"))
}
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 TestFilterDeprecatedFiles_ContainsDeprecatedFiles_DeprecatedFiltered(t *testing.T) {
test := func(name string, inputFiles, expected []string) {
t.Run(name, func(t *testing.T) {
paths := filterDeprecatedFiles(inputFiles)
assert.Equal(t, expected, paths)
})
}
test("OneDeprecated",
[]string{"include/core/SkDrawLooper.h"},
[]string{})
test("MultipleDeprecated",
[]string{
"include/core/SkDrawLooper.h",
"include/effects/SkBlurDrawLooper.h"},
[]string{})
test("FirstDeprecated",
[]string{
"include/core/SkDrawLooper.h",
"not/deprecated/file.h"},
[]string{"not/deprecated/file.h"})
test("LastDeprecated",
[]string{
"not/deprecated/file.h",
"include/core/SkDrawLooper.h"},
[]string{"not/deprecated/file.h"})
}
func TestFilterDeprecatedFiles_NoDeprecatedFiles_SliceUnchanged(t *testing.T) {
test := func(name string, inputFiles, expected []string) {
t.Run(name, func(t *testing.T) {
paths := filterDeprecatedFiles(inputFiles)
assert.Equal(t, expected, paths)
})
}
test("EmptySlice", nil, []string{})
test("NoneDeprecated",
[]string{
"not/deprecated/file.h",
"also/not/deprecated/file.h"},
[]string{
"not/deprecated/file.h",
"also/not/deprecated/file.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("/"))
}