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 47skia_core_sources += skia_skpicture_sources 48 49skia_core_public += skia_pathops_public 50skia_core_public += skia_skpicture_public 51` 52 53var exportDescs = []GNIExportDesc{ 54 {GNI: "gn/core.gni", Vars: []GNIFileListExportDesc{ 55 {Var: "skia_core_sources", 56 Rules: []string{ 57 "//src/core:core_srcs", 58 "//src/opts:private_hdrs", 59 }}}, 60 }, 61} 62 63var testExporterParams = GNIExporterParams{ 64 WorkspaceDir: "/path/to/workspace", 65 ExportDescs: exportDescs, 66} 67 68func createCoreSourcesQueryResult() *build.QueryResult { 69 qr := build.QueryResult{} 70 ruleDesc := build.Target_RULE 71 72 // Rule #1 73 srcs := []string{ 74 "//src/core:SkAAClip.cpp", 75 "//src/core:SkATrace.cpp", 76 "//src/core:SkAlphaRuns.cpp", 77 } 78 r1 := createTestBuildRule("//src/core:core_srcs", "filegroup", 79 "/path/to/workspace/src/core/BUILD.bazel:376:20", srcs) 80 t1 := build.Target{Rule: r1, Type: &ruleDesc} 81 qr.Target = append(qr.Target, &t1) 82 83 // Rule #2 84 srcs = []string{ 85 "//src/opts:SkBitmapProcState_opts.h", 86 "//src/opts:SkBlitMask_opts.h", 87 "//src/opts:SkBlitRow_opts.h", 88 } 89 r2 := createTestBuildRule("//src/opts:private_hdrs", "filegroup", 90 "/path/to/workspace/src/opts/BUILD.bazel:26:10", srcs) 91 t2 := build.Target{Rule: r2, Type: &ruleDesc} 92 qr.Target = append(qr.Target, &t2) 93 return &qr 94} 95 96func TestGNIExporterExport_ValidInput_Success(t *testing.T) { 97 qr := createCoreSourcesQueryResult() 98 protoData, err := proto.Marshal(qr) 99 require.NoError(t, err) 100 101 fs := mocks.NewFileSystem(t) 102 var contents bytes.Buffer 103 fs.On("OpenFile", mock.Anything).Once().Run(func(args mock.Arguments) { 104 path := args.String(0) 105 assert.True(t, filepath.IsAbs(path)) 106 assert.Equal(t, "/path/to/workspace/gn/core.gni", filepath.ToSlash(path)) 107 }).Return(&contents, nil).Once() 108 e := NewGNIExporter(testExporterParams, fs) 109 qcmd := mocks.NewQueryCommand(t) 110 qcmd.On("Read", mock.Anything).Return(protoData, nil).Once() 111 err = e.Export(qcmd) 112 require.NoError(t, err) 113 114 assert.Equal(t, publicSrcsExpectedGNI, contents.String()) 115} 116 117func TestMakeRelativeFilePathForGNI_MatchingRootDir_Success(t *testing.T) { 118 test := func(name, target, expectedPath string) { 119 t.Run(name, func(t *testing.T) { 120 path, err := makeRelativeFilePathForGNI(target) 121 require.NoError(t, err) 122 assert.Equal(t, expectedPath, path) 123 }) 124 } 125 126 test("src", "src/core/file.cpp", "$_src/core/file.cpp") 127 test("include", "include/core/file.h", "$_include/core/file.h") 128 test("modules", "modules/mod/file.cpp", "$_modules/mod/file.cpp") 129} 130 131func TestMakeRelativeFilePathForGNI_IndalidInput_ReturnError(t *testing.T) { 132 test := func(name, target string) { 133 t.Run(name, func(t *testing.T) { 134 _, err := makeRelativeFilePathForGNI(target) 135 assert.Error(t, err) 136 }) 137 } 138 139 test("EmptyString", "") 140 test("UnsupportedRootDir", "//valid/rule/incorrect/root/dir:file.cpp") 141} 142 143func TestIsHeaderFile_HeaderFiles_ReturnTrue(t *testing.T) { 144 test := func(name, path string) { 145 t.Run(name, func(t *testing.T) { 146 assert.True(t, isHeaderFile(path)) 147 }) 148 } 149 150 test("LowerH", "path/to/file.h") 151 test("UpperH", "path/to/file.H") 152 test("MixedHpp", "path/to/file.Hpp") 153} 154 155func TestIsHeaderFile_NonHeaderFiles_ReturnTrue(t *testing.T) { 156 test := func(name, path string) { 157 t.Run(name, func(t *testing.T) { 158 assert.False(t, isHeaderFile(path)) 159 }) 160 } 161 162 test("EmptyString", "") 163 test("DirPath", "/path/to/dir") 164 test("C++Source", "/path/to/file.cpp") 165 test("DotHInDir", "/path/to/dir.h/file.cpp") 166 test("Go", "main.go") 167} 168 169func TestFileListContainsOnlyCppHeaderFiles_AllHeaders_ReturnsTrue(t *testing.T) { 170 test := func(name string, paths []string) { 171 t.Run(name, func(t *testing.T) { 172 assert.True(t, fileListContainsOnlyCppHeaderFiles(paths)) 173 }) 174 } 175 176 test("OneFile", []string{"file.h"}) 177 test("Multiple", []string{"file.h", "foo.hpp"}) 178} 179 180func TestFileListContainsOnlyCppHeaderFiles_NotAllHeaders_ReturnsFalse(t *testing.T) { 181 test := func(name string, paths []string) { 182 t.Run(name, func(t *testing.T) { 183 assert.False(t, fileListContainsOnlyCppHeaderFiles(paths)) 184 }) 185 } 186 187 test("Nil", nil) 188 test("HeaderFiles", []string{"file.h", "file2.cpp"}) 189 test("GoFile", []string{"file.go"}) 190} 191 192func TestWorkspaceToAbsPath_ReturnsAbsolutePath(t *testing.T) { 193 fs := mocks.NewFileSystem(t) 194 e := NewGNIExporter(testExporterParams, fs) 195 require.NotNil(t, e) 196 197 test := func(name, input, expected string) { 198 t.Run(name, func(t *testing.T) { 199 assert.Equal(t, expected, e.workspaceToAbsPath(input)) 200 }) 201 } 202 203 test("FileInDir", "foo/bar.txt", "/path/to/workspace/foo/bar.txt") 204 test("DirInDir", "foo/bar", "/path/to/workspace/foo/bar") 205 test("RootFile", "root.txt", "/path/to/workspace/root.txt") 206 test("WorkspaceDir", "", "/path/to/workspace") 207} 208 209func TestAbsToWorkspacePath_PathInWorkspace_ReturnsRelativePath(t *testing.T) { 210 fs := mocks.NewFileSystem(t) 211 e := NewGNIExporter(testExporterParams, fs) 212 require.NotNil(t, e) 213 214 test := func(name, input, expected string) { 215 t.Run(name, func(t *testing.T) { 216 path, err := e.absToWorkspacePath(input) 217 assert.NoError(t, err) 218 assert.Equal(t, expected, path) 219 }) 220 } 221 222 test("FileInDir", "/path/to/workspace/foo/bar.txt", "foo/bar.txt") 223 test("DirInDir", "/path/to/workspace/foo/bar", "foo/bar") 224 test("RootFile", "/path/to/workspace/root.txt", "root.txt") 225 test("RootFile", "/path/to/workspace/世界", "世界") 226 test("WorkspaceDir", "/path/to/workspace", "") 227} 228 229func TestAbsToWorkspacePath_PathNotInWorkspace_ReturnsError(t *testing.T) { 230 fs := mocks.NewFileSystem(t) 231 e := NewGNIExporter(testExporterParams, fs) 232 require.NotNil(t, e) 233 234 _, err := e.absToWorkspacePath("/path/to/file.txt") 235 assert.Error(t, err) 236} 237 238func TestGetGNILineVariable_LinesWithVariables_ReturnVariable(t *testing.T) { 239 test := func(name, inputLine, expected string) { 240 t.Run(name, func(t *testing.T) { 241 assert.Equal(t, expected, getGNILineVariable(inputLine)) 242 }) 243 } 244 245 test("EqualWithSpaces", `foo = [ "something" ]`, "foo") 246 test("EqualNoSpaces", `foo=[ "something" ]`, "foo") 247 test("EqualSpaceBefore", `foo =[ "something" ]`, "foo") 248 test("MultilineList", `foo = [`, "foo") 249} 250 251func TestGetGNILineVariable_LinesWithVariables_NoMatch(t *testing.T) { 252 test := func(name, inputLine, expected string) { 253 t.Run(name, func(t *testing.T) { 254 assert.Equal(t, expected, getGNILineVariable(inputLine)) 255 }) 256 } 257 258 test("FirstCharSpace", ` foo = [ "something" ]`, "") // Impl. requires formatted file. 259 test("NotList", `foo = bar`, "") 260 test("ListLiteral", `[ "something" ]`, "") 261 test("ListInComment", `# foo = [ "something" ]`, "") 262 test("MissingVariable", `=[ "something" ]`, "") 263 test("EmptyString", ``, "") 264} 265 266func TestFoo_DeprecatedFiles_ReturnsTrue(t *testing.T) { 267 assert.True(t, isSourceFileDeprecated("include/core/SkDrawLooper.h")) 268} 269 270func TestFoo_NotDeprecatedFiles_ReturnsFalse(t *testing.T) { 271 assert.False(t, isSourceFileDeprecated("include/core/SkColor.h")) 272} 273 274func TestExtractTopLevelFolder_PathsWithTopDir_ReturnsTopDir(t *testing.T) { 275 test := func(name, input, expected string) { 276 t.Run(name, func(t *testing.T) { 277 assert.Equal(t, expected, extractTopLevelFolder(input)) 278 }) 279 } 280 test("TopIsDir", "foo/bar/baz.txt", "foo") 281 test("TopIsVariable", "$_src/bar/baz.txt", "$_src") 282 test("TopIsFile", "baz.txt", "baz.txt") 283 test("TopIsAbsDir", "/foo/bar/baz.txt", "") 284} 285 286func TestExtractTopLevelFolder_PathsWithNoTopDir_ReturnsEmptyString(t *testing.T) { 287 test := func(name, input, expected string) { 288 t.Run(name, func(t *testing.T) { 289 assert.Equal(t, expected, extractTopLevelFolder(input)) 290 }) 291 } 292 test("EmptyString", "", "") 293 test("EmptyAbsRoot", "/", "") 294 test("MultipleSlashes", "///", "") 295} 296 297func TestAddGNIVariablesToWorkspacePaths_ValidInput_ReturnsVariables(t *testing.T) { 298 test := func(name string, inputPaths, expected []string) { 299 t.Run(name, func(t *testing.T) { 300 gniPaths, err := addGNIVariablesToWorkspacePaths(inputPaths) 301 require.NoError(t, err) 302 assert.Equal(t, expected, gniPaths) 303 }) 304 } 305 test("EmptySlice", nil, []string{}) 306 test("AllVariables", 307 []string{"src/include/foo.h", 308 "include/foo.h", 309 "modules/foo.cpp"}, 310 []string{"$_src/include/foo.h", 311 "$_include/foo.h", 312 "$_modules/foo.cpp"}) 313} 314 315func TestAddGNIVariablesToWorkspacePaths_InvalidInput_ReturnsError(t *testing.T) { 316 test := func(name string, inputPaths []string) { 317 t.Run(name, func(t *testing.T) { 318 _, err := addGNIVariablesToWorkspacePaths(inputPaths) 319 assert.Error(t, err) 320 }) 321 } 322 test("InvalidTopDir", []string{"nomatch/include/foo.h"}) 323 test("RuleNotPath", []string{"//src/core:source.cpp"}) 324} 325 326func TestConvertTargetsToFilePaths_ValidInput_ReturnsPaths(t *testing.T) { 327 test := func(name string, inputTargets, expected []string) { 328 t.Run(name, func(t *testing.T) { 329 paths, err := convertTargetsToFilePaths(inputTargets) 330 require.NoError(t, err) 331 assert.Equal(t, expected, paths) 332 }) 333 } 334 test("EmptySlice", nil, []string{}) 335 test("Files", 336 []string{"//src/include:foo.h", 337 "//include:foo.h", 338 "//modules:foo.cpp"}, 339 []string{"src/include/foo.h", 340 "include/foo.h", 341 "modules/foo.cpp"}) 342} 343 344func TestConvertTargetsToFilePaths_InvalidInput_ReturnsError(t *testing.T) { 345 test := func(name string, inputTargets []string) { 346 t.Run(name, func(t *testing.T) { 347 _, err := convertTargetsToFilePaths(inputTargets) 348 assert.Error(t, err) 349 }) 350 } 351 test("EmptyString", []string{""}) 352 test("ValidTargetEmptyString", []string{"//src/include:foo.h", ""}) 353 test("EmptyStringValidTarget", []string{"//src/include:foo.h", ""}) 354} 355 356func TestFilterDeprecatedFiles_ContainsDeprecatedFiles_DeprecatedFiltered(t *testing.T) { 357 test := func(name string, inputFiles, expected []string) { 358 t.Run(name, func(t *testing.T) { 359 paths := filterDeprecatedFiles(inputFiles) 360 assert.Equal(t, expected, paths) 361 }) 362 } 363 test("OneDeprecated", 364 []string{"include/core/SkDrawLooper.h"}, 365 []string{}) 366 test("MultipleDeprecated", 367 []string{ 368 "include/core/SkDrawLooper.h", 369 "include/effects/SkBlurDrawLooper.h"}, 370 []string{}) 371 test("FirstDeprecated", 372 []string{ 373 "include/core/SkDrawLooper.h", 374 "not/deprecated/file.h"}, 375 []string{"not/deprecated/file.h"}) 376 test("LastDeprecated", 377 []string{ 378 "not/deprecated/file.h", 379 "include/core/SkDrawLooper.h"}, 380 []string{"not/deprecated/file.h"}) 381} 382 383func TestFilterDeprecatedFiles_NoDeprecatedFiles_SliceUnchanged(t *testing.T) { 384 test := func(name string, inputFiles, expected []string) { 385 t.Run(name, func(t *testing.T) { 386 paths := filterDeprecatedFiles(inputFiles) 387 assert.Equal(t, expected, paths) 388 }) 389 } 390 test("EmptySlice", nil, []string{}) 391 test("NoneDeprecated", 392 []string{ 393 "not/deprecated/file.h", 394 "also/not/deprecated/file.h"}, 395 []string{ 396 "not/deprecated/file.h", 397 "also/not/deprecated/file.h"}) 398} 399 400func TestRemoveDuplicate_ContainsDuplicates_SortedAndDuplicatesRemoved(t *testing.T) { 401 files := []string{ 402 "alpha", 403 "beta", 404 "gamma", 405 "delta", 406 "beta", 407 "Alpha", 408 "alpha", 409 "path/to/file", 410 "path/to/file2", 411 "path/to/file", 412 } 413 output := removeDuplicates(files) 414 assert.Equal(t, []string{ 415 "Alpha", 416 "alpha", 417 "beta", 418 "delta", 419 "gamma", 420 "path/to/file", 421 "path/to/file2", 422 }, output) 423} 424 425func TestRemoveDuplicates_NoDuplicates_ReturnsOnlySorted(t *testing.T) { 426 files := []string{ 427 "Beta", 428 "ALPHA", 429 "gamma", 430 "path/to/file2", 431 "path/to/file", 432 } 433 output := removeDuplicates(files) 434 assert.Equal(t, []string{ 435 "ALPHA", 436 "Beta", 437 "gamma", 438 "path/to/file", 439 "path/to/file2", 440 }, output) 441} 442 443func TestGetPathToTopDir_ValidRelativePaths_ReturnsExpected(t *testing.T) { 444 test := func(name, expected, input string) { 445 t.Run(name, func(t *testing.T) { 446 assert.Equal(t, expected, getPathToTopDir(input)) 447 }) 448 } 449 test("TopDir", ".", "core.gni") 450 test("OneDown", "..", "gn/core.gni") 451 test("TwoDown", "../..", "modules/skcms/skcms.gni") 452} 453 454func TestGetPathToTopDir_AbsolutePath_ReturnsEmptyString(t *testing.T) { 455 // Exporter shouldn't use absolute paths, but just to be safe. 456 assert.Equal(t, "", getPathToTopDir("/")) 457} 458