• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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