xref: /aosp_15_r20/external/skia/bazel/exporter/gni_exporter_test.go (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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