xref: /aosp_15_r20/build/soong/cc/tidy_test.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright 2022 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package cc
16
17import (
18	"fmt"
19	"strings"
20	"testing"
21
22	"android/soong/android"
23)
24
25func TestTidyFlagsWarningsAsErrors(t *testing.T) {
26	// The "tidy_flags" property should not contain -warnings-as-errors.
27	type testCase struct {
28		libName, bp string
29		errorMsg    string   // a negative test; must have error message
30		flags       []string // must have substrings in tidyFlags
31		noFlags     []string // must not have substrings in tidyFlags
32	}
33
34	testCases := []testCase{
35		{
36			"libfoo1",
37			`cc_library_shared { // no warnings-as-errors, good tidy_flags
38			  name: "libfoo1",
39			  srcs: ["foo.c"],
40              tidy_flags: ["-header-filter=dir1/"],
41		    }`,
42			"",
43			[]string{"-header-filter=dir1/"},
44			[]string{"-warnings-as-errors"},
45		},
46		{
47			"libfoo2",
48			`cc_library_shared { // good use of tidy_checks_as_errors
49			  name: "libfoo2",
50			  srcs: ["foo.c"],
51			  tidy_checks_as_errors: ["xyz-*", "abc"],
52		    }`,
53			"",
54			[]string{
55				"-header-filter=^", // there is a default header filter
56				"-warnings-as-errors='xyz-*',abc,${config.TidyGlobalNoErrorChecks}",
57			},
58			[]string{},
59		},
60	}
61	if NoWarningsAsErrorsInTidyFlags {
62		testCases = append(testCases, testCase{
63			"libfoo3",
64			`cc_library_shared { // bad use of -warnings-as-errors in tidy_flags
65					  name: "libfoo3",
66					  srcs: ["foo.c"],
67		              tidy_flags: [
68		                "-header-filters=.*",
69					    "-warnings-as-errors=xyz-*",
70		              ],
71				    }`,
72			`module "libfoo3" .*: tidy_flags: should not contain .*;` +
73				` use tidy_checks_as_errors instead`,
74			[]string{},
75			[]string{},
76		})
77	}
78	for _, test := range testCases {
79		if test.errorMsg != "" {
80			testCcError(t, test.errorMsg, test.bp)
81			continue
82		}
83		variant := "android_arm64_armv8-a_shared"
84		ctx := testCc(t, test.bp)
85		t.Run("caseTidyFlags", func(t *testing.T) {
86			flags := ctx.ModuleForTests(test.libName, variant).Rule("clangTidy").Args["tidyFlags"]
87			for _, flag := range test.flags {
88				if !strings.Contains(flags, flag) {
89					t.Errorf("tidyFlags %v for %s does not contain %s.", flags, test.libName, flag)
90				}
91			}
92			for _, flag := range test.noFlags {
93				if strings.Contains(flags, flag) {
94					t.Errorf("tidyFlags %v for %s should not contain %s.", flags, test.libName, flag)
95				}
96			}
97		})
98	}
99}
100
101func TestTidyChecks(t *testing.T) {
102	// The "tidy_checks" property defines additional checks appended
103	// to global default. But there are some checks disabled after
104	// the local tidy_checks.
105	bp := `
106		cc_library_shared { // has global checks + extraGlobalChecks
107			name: "libfoo_1",
108			srcs: ["foo.c"],
109		}
110		cc_library_shared { // has only local checks + extraGlobalChecks
111			name: "libfoo_2",
112			srcs: ["foo.c"],
113			tidy_checks: ["-*", "xyz-*"],
114		}
115		cc_library_shared { // has global checks + local checks + extraGlobalChecks
116			name: "libfoo_3",
117			srcs: ["foo.c"],
118			tidy_checks: ["-abc*", "xyz-*", "mycheck"],
119		}
120		cc_library_shared { // has only local checks after "-*" + extraGlobalChecks
121			name: "libfoo_4",
122			srcs: ["foo.c"],
123			tidy_checks: ["-abc*", "xyz-*", "mycheck", "-*", "xyz-*"],
124		}`
125	ctx := testCc(t, bp)
126
127	globalChecks := "-checks=${config.TidyDefaultGlobalChecks},"
128	firstXyzChecks := "-checks='-*','xyz-*',"
129	localXyzChecks := "'-*','xyz-*'"
130	localAbcChecks := "'-abc*','xyz-*',mycheck"
131	extraGlobalChecks := ",${config.TidyGlobalNoChecks}"
132	testCases := []struct {
133		libNumber int      // 1,2,3,...
134		checks    []string // must have substrings in -checks
135		noChecks  []string // must not have substrings in -checks
136	}{
137		{1, []string{globalChecks, extraGlobalChecks}, []string{localXyzChecks, localAbcChecks}},
138		{2, []string{firstXyzChecks, extraGlobalChecks}, []string{globalChecks, localAbcChecks}},
139		{3, []string{globalChecks, localAbcChecks, extraGlobalChecks}, []string{localXyzChecks}},
140		{4, []string{firstXyzChecks, extraGlobalChecks}, []string{globalChecks, localAbcChecks}},
141	}
142	t.Run("caseTidyChecks", func(t *testing.T) {
143		variant := "android_arm64_armv8-a_shared"
144		for _, test := range testCases {
145			libName := fmt.Sprintf("libfoo_%d", test.libNumber)
146			flags := ctx.ModuleForTests(libName, variant).Rule("clangTidy").Args["tidyFlags"]
147			splitFlags := strings.Split(flags, " ")
148			foundCheckFlag := false
149			for _, flag := range splitFlags {
150				if strings.HasPrefix(flag, "-checks=") {
151					foundCheckFlag = true
152					for _, check := range test.checks {
153						if !strings.Contains(flag, check) {
154							t.Errorf("tidyFlags for %s does not contain %s.", libName, check)
155						}
156					}
157					for _, check := range test.noChecks {
158						if strings.Contains(flag, check) {
159							t.Errorf("tidyFlags for %s should not contain %s.", libName, check)
160						}
161					}
162					break
163				}
164			}
165			if !foundCheckFlag {
166				t.Errorf("tidyFlags for %s does not contain -checks=.", libName)
167			}
168		}
169	})
170}
171
172func TestWithTidy(t *testing.T) {
173	// When WITH_TIDY=1 or (ALLOW_LOCAL_TIDY_TRUE=1 and local tidy:true)
174	// a C++ library should depend on .tidy files.
175	testCases := []struct {
176		withTidy, allowLocalTidyTrue string // "_" means undefined
177		needTidyFile                 []bool // for {libfoo_0, libfoo_1} and {libbar_0, libbar_1}
178	}{
179		{"_", "_", []bool{false, false, false}},
180		{"_", "0", []bool{false, false, false}},
181		{"_", "1", []bool{false, true, false}},
182		{"_", "true", []bool{false, true, false}},
183		{"0", "_", []bool{false, false, false}},
184		{"0", "1", []bool{false, true, false}},
185		{"1", "_", []bool{true, true, false}},
186		{"1", "false", []bool{true, true, false}},
187		{"1", "1", []bool{true, true, false}},
188		{"true", "_", []bool{true, true, false}},
189	}
190	bp := `
191		cc_library_shared {
192			name: "libfoo_0", // depends on .tidy if WITH_TIDY=1
193			srcs: ["foo.c"],
194		}
195		cc_library_shared { // depends on .tidy if WITH_TIDY=1 or ALLOW_LOCAL_TIDY_TRUE=1
196			name: "libfoo_1",
197			srcs: ["foo.c"],
198			tidy: true,
199		}
200		cc_library_shared { // no .tidy
201			name: "libfoo_2",
202			srcs: ["foo.c"],
203			tidy: false,
204		}
205		cc_library_static {
206			name: "libbar_0", // depends on .tidy if WITH_TIDY=1
207			srcs: ["bar.c"],
208		}
209		cc_library_static { // depends on .tidy if WITH_TIDY=1 or ALLOW_LOCAL_TIDY_TRUE=1
210			name: "libbar_1",
211			srcs: ["bar.c"],
212			tidy: true,
213		}
214		cc_library_static { // no .tidy
215			name: "libbar_2",
216			srcs: ["bar.c"],
217			tidy: false,
218		}`
219	for index, test := range testCases {
220		testName := fmt.Sprintf("case%d,%v,%v", index, test.withTidy, test.allowLocalTidyTrue)
221		t.Run(testName, func(t *testing.T) {
222			testEnv := map[string]string{}
223			if test.withTidy != "_" {
224				testEnv["WITH_TIDY"] = test.withTidy
225			}
226			if test.allowLocalTidyTrue != "_" {
227				testEnv["ALLOW_LOCAL_TIDY_TRUE"] = test.allowLocalTidyTrue
228			}
229			ctx := android.GroupFixturePreparers(prepareForCcTest, android.FixtureMergeEnv(testEnv)).RunTestWithBp(t, bp)
230			for n := 0; n < 3; n++ {
231				checkLibraryRule := func(foo, variant, ruleName string) {
232					libName := fmt.Sprintf("lib%s_%d", foo, n)
233					tidyFile := "out/soong/.intermediates/" + libName + "/" + variant + "/obj/" + foo + ".tidy"
234					depFiles := ctx.ModuleForTests(libName, variant).Rule(ruleName).Validations.Strings()
235					if test.needTidyFile[n] {
236						android.AssertStringListContains(t, libName+" needs .tidy file", depFiles, tidyFile)
237					} else {
238						android.AssertStringListDoesNotContain(t, libName+" does not need .tidy file", depFiles, tidyFile)
239					}
240				}
241				checkLibraryRule("foo", "android_arm64_armv8-a_shared", "ld")
242				checkLibraryRule("bar", "android_arm64_armv8-a_static", "ar")
243			}
244		})
245	}
246}
247
248func TestWithGeneratedCode(t *testing.T) {
249	bp := `
250		cc_library_shared {
251			name: "libfoo",
252			srcs: ["foo_1.y", "foo_2.yy", "foo_3.l", "foo_4.ll", "foo_5.proto",
253			       "foo_6.aidl", "foo_7.rscript", "foo_8.fs", "foo_9.sysprop",
254			       "foo_src.cpp"],
255			tidy: true,
256		}`
257	variant := "android_arm64_armv8-a_shared"
258
259	testEnv := map[string]string{}
260	testEnv["ALLOW_LOCAL_TIDY_TRUE"] = "1"
261
262	ctx := android.GroupFixturePreparers(prepareForCcTest, android.FixtureMergeEnv(testEnv)).RunTestWithBp(t, bp)
263
264	t.Run("tidy should be only run for source code, not for generated code", func(t *testing.T) {
265		depFiles := ctx.ModuleForTests("libfoo", variant).Rule("ld").Validations.Strings()
266
267		tidyFileForCpp := "out/soong/.intermediates/libfoo/" + variant + "/obj/foo_src.tidy"
268
269		android.AssertArrayString(t,
270			"only one .tidy file for source code should exist for libfoo",
271			[]string{tidyFileForCpp}, depFiles)
272	})
273}
274