1// Copyright 2022 Google LLC 2// 3// Use of this source code is governed by a BSD-style license that can be 4// found in the LICENSE file. 5 6package exporter 7 8import ( 9 "bytes" 10 "path/filepath" 11 "testing" 12 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/mock" 15 "github.com/stretchr/testify/require" 16 "go.skia.org/skia/bazel/exporter/build_proto/build" 17 "go.skia.org/skia/bazel/exporter/interfaces/mocks" 18 "google.golang.org/protobuf/proto" 19) 20 21// The expected gn/core.gni file contents for createCoreSourcesQueryResult(). 22// This expected result is handmade. 23const publicSrcsExpectedGNI = `# DO NOT EDIT: This is a generated file. 24# See //bazel/exporter_tool/README.md for more information. 25# 26# The sources of truth are: 27# //src/core/BUILD.bazel 28# //src/opts/BUILD.bazel 29 30# To update this file, run make -C bazel generate_gni 31 32_src = get_path_info("../src", "abspath") 33 34# List generated by Bazel rules: 35# //src/core:core_srcs 36# //src/opts:private_hdrs 37skia_core_sources = [ 38 "$_src/core/SkAAClip.cpp", 39 "$_src/core/SkATrace.cpp", 40 "$_src/core/SkAlphaRuns.cpp", 41 "$_src/opts/SkBitmapProcState_opts.h", 42 "$_src/opts/SkBlitMask_opts.h", 43 "$_src/opts/SkBlitRow_opts.h", 44] 45 46skia_core_sources += skia_pathops_sources 47 48skia_core_public += skia_pathops_public 49 50` 51 52var exportDescs = []GNIExportDesc{ 53 {GNI: "gn/core.gni", Vars: []GNIFileListExportDesc{ 54 {Var: "skia_core_sources", 55 Rules: []string{ 56 "//src/core:core_srcs", 57 "//src/opts:private_hdrs", 58 }}}, 59 }, 60} 61 62var testExporterParams = GNIExporterParams{ 63 WorkspaceDir: "/path/to/workspace", 64 ExportDescs: exportDescs, 65} 66 67func createCoreSourcesQueryResult() *build.QueryResult { 68 qr := build.QueryResult{} 69 ruleDesc := build.Target_RULE 70 71 // Rule #1 72 srcs := []string{ 73 "//src/core:SkAAClip.cpp", 74 "//src/core:SkATrace.cpp", 75 "//src/core:SkAlphaRuns.cpp", 76 } 77 r1 := createTestBuildRule("//src/core:core_srcs", "filegroup", 78 "/path/to/workspace/src/core/BUILD.bazel:376:20", srcs) 79 t1 := build.Target{Rule: r1, Type: &ruleDesc} 80 qr.Target = append(qr.Target, &t1) 81 82 // Rule #2 83 srcs = []string{ 84 "//src/opts:SkBitmapProcState_opts.h", 85 "//src/opts:SkBlitMask_opts.h", 86 "//src/opts:SkBlitRow_opts.h", 87 } 88 r2 := createTestBuildRule("//src/opts:private_hdrs", "filegroup", 89 "/path/to/workspace/src/opts/BUILD.bazel:26:10", srcs) 90 t2 := build.Target{Rule: r2, Type: &ruleDesc} 91 qr.Target = append(qr.Target, &t2) 92 return &qr 93} 94 95func TestGNIExporterExport_ValidInput_Success(t *testing.T) { 96 qr := createCoreSourcesQueryResult() 97 protoData, err := proto.Marshal(qr) 98 require.NoError(t, err) 99 100 fs := mocks.NewFileSystem(t) 101 var contents bytes.Buffer 102 fs.On("OpenFile", mock.Anything).Once().Run(func(args mock.Arguments) { 103 path := args.String(0) 104 assert.True(t, filepath.IsAbs(path)) 105 assert.Equal(t, "/path/to/workspace/gn/core.gni", filepath.ToSlash(path)) 106 }).Return(&contents, nil).Once() 107 e := NewGNIExporter(testExporterParams, fs) 108 qcmd := mocks.NewQueryCommand(t) 109 qcmd.On("Read", mock.Anything).Return(protoData, nil).Once() 110 err = e.Export(qcmd) 111 require.NoError(t, err) 112 113 assert.Equal(t, publicSrcsExpectedGNI, contents.String()) 114} 115 116func TestMakeRelativeFilePathForGNI_MatchingRootDir_Success(t *testing.T) { 117 test := func(name, target, expectedPath string) { 118 t.Run(name, func(t *testing.T) { 119 path, err := makeRelativeFilePathForGNI(target) 120 require.NoError(t, err) 121 assert.Equal(t, expectedPath, path) 122 }) 123 } 124 125 test("src", "src/core/file.cpp", "$_src/core/file.cpp") 126 test("include", "include/core/file.h", "$_include/core/file.h") 127 test("modules", "modules/mod/file.cpp", "$_modules/mod/file.cpp") 128} 129 130func TestMakeRelativeFilePathForGNI_IndalidInput_ReturnError(t *testing.T) { 131 test := func(name, target string) { 132 t.Run(name, func(t *testing.T) { 133 _, err := makeRelativeFilePathForGNI(target) 134 assert.Error(t, err) 135 }) 136 } 137 138 test("EmptyString", "") 139 test("UnsupportedRootDir", "//valid/rule/incorrect/root/dir:file.cpp") 140} 141 142func TestIsHeaderFile_HeaderFiles_ReturnTrue(t *testing.T) { 143 test := func(name, path string) { 144 t.Run(name, func(t *testing.T) { 145 assert.True(t, isHeaderFile(path)) 146 }) 147 } 148 149 test("LowerH", "path/to/file.h") 150 test("UpperH", "path/to/file.H") 151 test("MixedHpp", "path/to/file.Hpp") 152} 153 154func TestIsHeaderFile_NonHeaderFiles_ReturnTrue(t *testing.T) { 155 test := func(name, path string) { 156 t.Run(name, func(t *testing.T) { 157 assert.False(t, isHeaderFile(path)) 158 }) 159 } 160 161 test("EmptyString", "") 162 test("DirPath", "/path/to/dir") 163 test("C++Source", "/path/to/file.cpp") 164 test("DotHInDir", "/path/to/dir.h/file.cpp") 165 test("Go", "main.go") 166} 167 168func TestFileListContainsOnlyCppHeaderFiles_AllHeaders_ReturnsTrue(t *testing.T) { 169 test := func(name string, paths []string) { 170 t.Run(name, func(t *testing.T) { 171 assert.True(t, fileListContainsOnlyCppHeaderFiles(paths)) 172 }) 173 } 174 175 test("OneFile", []string{"file.h"}) 176 test("Multiple", []string{"file.h", "foo.hpp"}) 177} 178 179func TestFileListContainsOnlyCppHeaderFiles_NotAllHeaders_ReturnsFalse(t *testing.T) { 180 test := func(name string, paths []string) { 181 t.Run(name, func(t *testing.T) { 182 assert.False(t, fileListContainsOnlyCppHeaderFiles(paths)) 183 }) 184 } 185 186 test("Nil", nil) 187 test("HeaderFiles", []string{"file.h", "file2.cpp"}) 188 test("GoFile", []string{"file.go"}) 189} 190 191func TestWorkspaceToAbsPath_ReturnsAbsolutePath(t *testing.T) { 192 fs := mocks.NewFileSystem(t) 193 e := NewGNIExporter(testExporterParams, fs) 194 require.NotNil(t, e) 195 196 test := func(name, input, expected string) { 197 t.Run(name, func(t *testing.T) { 198 assert.Equal(t, expected, e.workspaceToAbsPath(input)) 199 }) 200 } 201 202 test("FileInDir", "foo/bar.txt", "/path/to/workspace/foo/bar.txt") 203 test("DirInDir", "foo/bar", "/path/to/workspace/foo/bar") 204 test("RootFile", "root.txt", "/path/to/workspace/root.txt") 205 test("WorkspaceDir", "", "/path/to/workspace") 206} 207 208func TestAbsToWorkspacePath_PathInWorkspace_ReturnsRelativePath(t *testing.T) { 209 fs := mocks.NewFileSystem(t) 210 e := NewGNIExporter(testExporterParams, fs) 211 require.NotNil(t, e) 212 213 test := func(name, input, expected string) { 214 t.Run(name, func(t *testing.T) { 215 path, err := e.absToWorkspacePath(input) 216 assert.NoError(t, err) 217 assert.Equal(t, expected, path) 218 }) 219 } 220 221 test("FileInDir", "/path/to/workspace/foo/bar.txt", "foo/bar.txt") 222 test("DirInDir", "/path/to/workspace/foo/bar", "foo/bar") 223 test("RootFile", "/path/to/workspace/root.txt", "root.txt") 224 test("RootFile", "/path/to/workspace/世界", "世界") 225 test("WorkspaceDir", "/path/to/workspace", "") 226} 227 228func TestAbsToWorkspacePath_PathNotInWorkspace_ReturnsError(t *testing.T) { 229 fs := mocks.NewFileSystem(t) 230 e := NewGNIExporter(testExporterParams, fs) 231 require.NotNil(t, e) 232 233 _, err := e.absToWorkspacePath("/path/to/file.txt") 234 assert.Error(t, err) 235} 236 237func TestGetGNILineVariable_LinesWithVariables_ReturnVariable(t *testing.T) { 238 test := func(name, inputLine, expected string) { 239 t.Run(name, func(t *testing.T) { 240 assert.Equal(t, expected, getGNILineVariable(inputLine)) 241 }) 242 } 243 244 test("EqualWithSpaces", `foo = [ "something" ]`, "foo") 245 test("EqualNoSpaces", `foo=[ "something" ]`, "foo") 246 test("EqualSpaceBefore", `foo =[ "something" ]`, "foo") 247 test("MultilineList", `foo = [`, "foo") 248} 249 250func TestGetGNILineVariable_LinesWithVariables_NoMatch(t *testing.T) { 251 test := func(name, inputLine, expected string) { 252 t.Run(name, func(t *testing.T) { 253 assert.Equal(t, expected, getGNILineVariable(inputLine)) 254 }) 255 } 256 257 test("FirstCharSpace", ` foo = [ "something" ]`, "") // Impl. requires formatted file. 258 test("NotList", `foo = bar`, "") 259 test("ListLiteral", `[ "something" ]`, "") 260 test("ListInComment", `# foo = [ "something" ]`, "") 261 test("MissingVariable", `=[ "something" ]`, "") 262 test("EmptyString", ``, "") 263} 264 265func TestExtractTopLevelFolder_PathsWithTopDir_ReturnsTopDir(t *testing.T) { 266 test := func(name, input, expected string) { 267 t.Run(name, func(t *testing.T) { 268 assert.Equal(t, expected, extractTopLevelFolder(input)) 269 }) 270 } 271 test("TopIsDir", "foo/bar/baz.txt", "foo") 272 test("TopIsVariable", "$_src/bar/baz.txt", "$_src") 273 test("TopIsFile", "baz.txt", "baz.txt") 274 test("TopIsAbsDir", "/foo/bar/baz.txt", "") 275} 276 277func TestExtractTopLevelFolder_PathsWithNoTopDir_ReturnsEmptyString(t *testing.T) { 278 test := func(name, input, expected string) { 279 t.Run(name, func(t *testing.T) { 280 assert.Equal(t, expected, extractTopLevelFolder(input)) 281 }) 282 } 283 test("EmptyString", "", "") 284 test("EmptyAbsRoot", "/", "") 285 test("MultipleSlashes", "///", "") 286} 287 288func TestAddGNIVariablesToWorkspacePaths_ValidInput_ReturnsVariables(t *testing.T) { 289 test := func(name string, inputPaths, expected []string) { 290 t.Run(name, func(t *testing.T) { 291 gniPaths, err := addGNIVariablesToWorkspacePaths(inputPaths) 292 require.NoError(t, err) 293 assert.Equal(t, expected, gniPaths) 294 }) 295 } 296 test("EmptySlice", nil, []string{}) 297 test("AllVariables", 298 []string{"src/include/foo.h", 299 "include/foo.h", 300 "modules/foo.cpp"}, 301 []string{"$_src/include/foo.h", 302 "$_include/foo.h", 303 "$_modules/foo.cpp"}) 304} 305 306func TestAddGNIVariablesToWorkspacePaths_InvalidInput_ReturnsError(t *testing.T) { 307 test := func(name string, inputPaths []string) { 308 t.Run(name, func(t *testing.T) { 309 _, err := addGNIVariablesToWorkspacePaths(inputPaths) 310 assert.Error(t, err) 311 }) 312 } 313 test("InvalidTopDir", []string{"nomatch/include/foo.h"}) 314 test("RuleNotPath", []string{"//src/core:source.cpp"}) 315} 316 317func TestConvertTargetsToFilePaths_ValidInput_ReturnsPaths(t *testing.T) { 318 test := func(name string, inputTargets, expected []string) { 319 t.Run(name, func(t *testing.T) { 320 paths, err := convertTargetsToFilePaths(inputTargets) 321 require.NoError(t, err) 322 assert.Equal(t, expected, paths) 323 }) 324 } 325 test("EmptySlice", nil, []string{}) 326 test("Files", 327 []string{"//src/include:foo.h", 328 "//include:foo.h", 329 "//modules:foo.cpp"}, 330 []string{"src/include/foo.h", 331 "include/foo.h", 332 "modules/foo.cpp"}) 333} 334 335func TestConvertTargetsToFilePaths_InvalidInput_ReturnsError(t *testing.T) { 336 test := func(name string, inputTargets []string) { 337 t.Run(name, func(t *testing.T) { 338 _, err := convertTargetsToFilePaths(inputTargets) 339 assert.Error(t, err) 340 }) 341 } 342 test("EmptyString", []string{""}) 343 test("ValidTargetEmptyString", []string{"//src/include:foo.h", ""}) 344 test("EmptyStringValidTarget", []string{"//src/include:foo.h", ""}) 345} 346 347func TestRemoveDuplicate_ContainsDuplicates_SortedAndDuplicatesRemoved(t *testing.T) { 348 files := []string{ 349 "alpha", 350 "beta", 351 "gamma", 352 "delta", 353 "beta", 354 "Alpha", 355 "alpha", 356 "path/to/file", 357 "path/to/file2", 358 "path/to/file", 359 } 360 output := removeDuplicates(files) 361 assert.Equal(t, []string{ 362 "Alpha", 363 "alpha", 364 "beta", 365 "delta", 366 "gamma", 367 "path/to/file", 368 "path/to/file2", 369 }, output) 370} 371 372func TestRemoveDuplicates_NoDuplicates_ReturnsOnlySorted(t *testing.T) { 373 files := []string{ 374 "Beta", 375 "ALPHA", 376 "gamma", 377 "path/to/file2", 378 "path/to/file", 379 } 380 output := removeDuplicates(files) 381 assert.Equal(t, []string{ 382 "ALPHA", 383 "Beta", 384 "gamma", 385 "path/to/file", 386 "path/to/file2", 387 }, output) 388} 389 390func TestGetPathToTopDir_ValidRelativePaths_ReturnsExpected(t *testing.T) { 391 test := func(name, expected, input string) { 392 t.Run(name, func(t *testing.T) { 393 assert.Equal(t, expected, getPathToTopDir(input)) 394 }) 395 } 396 test("TopDir", ".", "core.gni") 397 test("OneDown", "..", "gn/core.gni") 398 test("TwoDown", "../..", "modules/skcms/skcms.gni") 399} 400 401func TestGetPathToTopDir_AbsolutePath_ReturnsEmptyString(t *testing.T) { 402 // Exporter shouldn't use absolute paths, but just to be safe. 403 assert.Equal(t, "", getPathToTopDir("/")) 404} 405