...

Source file src/github.com/bazelbuild/buildtools/warn/warn_test.go

Documentation: github.com/bazelbuild/buildtools/warn

     1  /*
     2  Copyright 2020 Google LLC
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      https://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package warn
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/bazelbuild/buildtools/build"
    26  	"github.com/bazelbuild/buildtools/testutils"
    27  )
    28  
    29  const (
    30  	scopeBuild       = build.TypeBuild
    31  	scopeBzl         = build.TypeBzl
    32  	scopeWorkspace   = build.TypeWorkspace
    33  	scopeDefault     = build.TypeDefault
    34  	scopeModule      = build.TypeModule
    35  	scopeEverywhere  = scopeBuild | scopeBzl | scopeWorkspace | scopeDefault | scopeModule
    36  	scopeBazel       = scopeBuild | scopeBzl | scopeWorkspace | scopeModule
    37  	scopeDeclarative = scopeBuild | scopeWorkspace | scopeModule
    38  )
    39  
    40  // A global FileReader object that can be used by tests. If a test redefines it it must
    41  // reset it when it finishes.
    42  var testFileReader *FileReader
    43  
    44  // A global variable containing package name for test cases. Can be overwritten
    45  // but must be reset when the test finishes.
    46  var testPackage string = "test/package"
    47  
    48  // fileReaderRequests is used by tests to check which files have actually been requested by testFileReader
    49  var fileReaderRequests []string
    50  
    51  func setUpFileReader(data map[string]string) (cleanup func()) {
    52  	readFile := func(filename string) ([]byte, error) {
    53  		fileReaderRequests = append(fileReaderRequests, filename)
    54  		if contents, ok := data[filename]; ok {
    55  			return []byte(contents), nil
    56  		}
    57  		return nil, fmt.Errorf("file not found")
    58  	}
    59  	testFileReader = NewFileReader(readFile)
    60  	fileReaderRequests = nil
    61  
    62  	return func() {
    63  		// Tear down
    64  		testFileReader = nil
    65  		fileReaderRequests = nil
    66  	}
    67  }
    68  
    69  func setUpTestPackage(name string) (cleanup func()) {
    70  	oldName := testPackage
    71  	testPackage = name
    72  
    73  	return func() {
    74  		// Tear down
    75  		testPackage = oldName
    76  	}
    77  }
    78  
    79  func getFilename(fileType build.FileType) string {
    80  	switch fileType {
    81  	case build.TypeBuild:
    82  		return "BUILD"
    83  	case build.TypeWorkspace:
    84  		return "WORKSPACE"
    85  	case build.TypeBzl:
    86  		return "test_file.bzl"
    87  	case build.TypeModule:
    88  		return "MODULE.bazel"
    89  	default:
    90  		return "test_file.strlrk"
    91  	}
    92  }
    93  
    94  func getFileForTest(input string, fileType build.FileType) *build.File {
    95  	input = strings.TrimLeft(input, "\n")
    96  	filename := getFilename(fileType)
    97  	file, err := build.Parse(testPackage+"/"+filename, []byte(input))
    98  	if err != nil {
    99  		panic(fmt.Sprintf("%v", err))
   100  	}
   101  	file.Pkg = testPackage
   102  	file.Label = filename
   103  	file.WorkspaceRoot = "/home/users/foo/bar"
   104  	return file
   105  }
   106  
   107  func getFindings(category, input string, fileType build.FileType) []*Finding {
   108  	file := getFileForTest(input, fileType)
   109  	return FileWarnings(file, []string{category}, nil, ModeWarn, testFileReader)
   110  }
   111  
   112  func compareFindings(t *testing.T, category, input string, expected []string, scope, fileType build.FileType) {
   113  	// If scope doesn't match the file type, no warnings are expected
   114  	if scope&fileType == 0 {
   115  		expected = []string{}
   116  	}
   117  
   118  	findings := getFindings(category, input, fileType)
   119  	// We ensure that there is the expected number of warnings.
   120  	// At the moment, we check only the line numbers.
   121  	if len(expected) != len(findings) {
   122  		t.Errorf("Input: %s", input)
   123  		t.Errorf("number of matches: %d, want %d", len(findings), len(expected))
   124  		for _, e := range expected {
   125  			t.Errorf("expected: %s", e)
   126  		}
   127  		for _, f := range findings {
   128  			t.Errorf("got: %d: %s", f.Start.Line, f.Message)
   129  		}
   130  		return
   131  	}
   132  	for i := range findings {
   133  		msg := fmt.Sprintf(":%d: %s", findings[i].Start.Line, findings[i].Message)
   134  		if !strings.Contains(msg, expected[i]) {
   135  			t.Errorf("Input: %s", input)
   136  			t.Errorf("got:  `%s`,\nwant: `%s`", msg, expected[i])
   137  		}
   138  	}
   139  }
   140  
   141  // checkFix makes sure that fixed file contents match the expected output
   142  func checkFix(t *testing.T, category, input, expected string, scope, fileType build.FileType) {
   143  	// If scope doesn't match the file type, no changes are expected
   144  	if scope&fileType == 0 {
   145  		expected = input
   146  	}
   147  
   148  	file := getFileForTest(input, fileType)
   149  	goldenFile := getFileForTest(expected, fileType)
   150  
   151  	FixWarnings(file, []string{category}, false, testFileReader)
   152  	have := build.Format(file)
   153  	want := build.Format(goldenFile)
   154  	if !bytes.Equal(have, want) {
   155  		t.Errorf("fixed a test (type %s) incorrectly:\ninput:\n%s\ndiff (-expected, +ours)\n",
   156  			fileType, input)
   157  		testutils.Tdiff(t, want, have)
   158  	}
   159  }
   160  
   161  // checkFix makes sure that the file contents don't change if a fix is not requested
   162  // (i.e. the warning functions have no side effects modifying the AST)
   163  func checkNoFix(t *testing.T, category, input string, fileType build.FileType) {
   164  	file := getFileForTest(input, fileType)
   165  	formatted := build.Format(file)
   166  
   167  	// No fixes expected
   168  	FileWarnings(file, []string{category}, nil, ModeWarn, testFileReader)
   169  	fixed := build.FormatWithoutRewriting(file)
   170  
   171  	if !bytes.Equal(formatted, fixed) {
   172  		t.Errorf("Modified a file (type %s) while getting warnings:\ninput:\n%s\ndiff (-before, +after)\n",
   173  			fileType, input)
   174  		testutils.Tdiff(t, formatted, fixed)
   175  	}
   176  }
   177  
   178  func checkFindings(t *testing.T, category, input string, expected []string, scope build.FileType) {
   179  	// The same as checkFindingsAndFix but ensure that fixes don't change the file (except for formatting)
   180  	checkFindingsAndFix(t, category, input, input, expected, scope)
   181  }
   182  
   183  func checkFindingsAndFix(t *testing.T, category, input, output string, expected []string, scope build.FileType) {
   184  	fileTypes := []build.FileType{
   185  		build.TypeDefault,
   186  		build.TypeBuild,
   187  		build.TypeWorkspace,
   188  		build.TypeBzl,
   189  		build.TypeModule,
   190  	}
   191  
   192  	for _, fileType := range fileTypes {
   193  		compareFindings(t, category, input, expected, scope, fileType)
   194  		checkFix(t, category, input, output, scope, fileType)
   195  		checkFix(t, category, output, output, scope, fileType)
   196  		checkNoFix(t, category, input, fileType)
   197  	}
   198  }
   199  
   200  func TestCalculateDifference(t *testing.T) {
   201  	tests := []struct {
   202  		before      string
   203  		after       string
   204  		start       int
   205  		end         int
   206  		replacement string
   207  	}{
   208  		{
   209  			before:      "asdf",
   210  			after:       "asxydf",
   211  			start:       2,
   212  			end:         2,
   213  			replacement: "xy",
   214  		},
   215  		{
   216  			before:      "asxydf",
   217  			after:       "asdf",
   218  			start:       2,
   219  			end:         4,
   220  			replacement: "",
   221  		},
   222  		{
   223  			before:      "asxydf",
   224  			after:       "asztdf",
   225  			start:       2,
   226  			end:         4,
   227  			replacement: "zt",
   228  		},
   229  		{
   230  			before:      "",
   231  			after:       "foobar",
   232  			start:       0,
   233  			end:         0,
   234  			replacement: "foobar",
   235  		},
   236  		{
   237  			before:      "foobar",
   238  			after:       "",
   239  			start:       0,
   240  			end:         6,
   241  			replacement: "",
   242  		},
   243  		{
   244  			before:      "qwerty",
   245  			after:       "asdfgh",
   246  			start:       0,
   247  			end:         6,
   248  			replacement: "asdfgh",
   249  		},
   250  		{
   251  			before:      "aa",
   252  			after:       "aaaa",
   253  			start:       2,
   254  			end:         2,
   255  			replacement: "aa",
   256  		},
   257  		{
   258  			before:      "aaaa",
   259  			after:       "aa",
   260  			start:       2,
   261  			end:         4,
   262  			replacement: "",
   263  		},
   264  		{
   265  			before:      "abc",
   266  			after:       "abdbc",
   267  			start:       2,
   268  			end:         2,
   269  			replacement: "db",
   270  		},
   271  		{
   272  			before:      "abdbc",
   273  			after:       "abc",
   274  			start:       2,
   275  			end:         4,
   276  			replacement: "",
   277  		},
   278  	}
   279  
   280  	for _, tc := range tests {
   281  		before := []byte(tc.before)
   282  		after := []byte(tc.after)
   283  
   284  		start, end, replacement := calculateDifference(&before, &after)
   285  
   286  		if start != tc.start || end != tc.end || replacement != tc.replacement {
   287  			t.Errorf("Wrong difference for %q and %q: want %d, %d, %q, got %d, %d, %q",
   288  				tc.before, tc.after, tc.start, tc.end, tc.replacement, start, end, replacement)
   289  		}
   290  	}
   291  }
   292  
   293  func TestSuggestions(t *testing.T) {
   294  	// Suggestions are not generated by individual warning functions but by the warnings framework.
   295  
   296  	contents := `foo()
   297  
   298  attr.bar(name = "bar", cfg = "data")
   299  
   300  attr.baz("baz", cfg = "data")
   301  `
   302  	f, err := build.ParseBzl("file.bzl", []byte(contents))
   303  	if err != nil {
   304  		t.Fatalf("Parse error: %v", err)
   305  	}
   306  
   307  	findings := FileWarnings(f, []string{"attr-cfg"}, nil, ModeSuggest, testFileReader)
   308  	want := []struct {
   309  		start       int
   310  		end         int
   311  		replacement string
   312  	}{
   313  		{
   314  			start:       28,
   315  			end:         42,
   316  			replacement: "",
   317  		},
   318  		{
   319  			start:       59,
   320  			end:         73,
   321  			replacement: "",
   322  		},
   323  	}
   324  
   325  	if len(findings) != len(want) {
   326  		t.Errorf("Expected %d findings, got %d", len(want), len(findings))
   327  	}
   328  
   329  	for i, f := range findings {
   330  		w := want[i]
   331  		if f.Replacement == nil {
   332  			t.Errorf("No replacement for finding %d", i)
   333  		}
   334  		r := f.Replacement
   335  		if r.Start != w.start || r.End != w.end || r.Content != w.replacement {
   336  			t.Errorf("Wrong replacement #%d, want %d, %d, %q, got %d, %d, %q",
   337  				i, w.start, w.end, w.replacement, r.Start, r.End, r.Content)
   338  		}
   339  	}
   340  }
   341  
   342  func TestDisabledWarning(t *testing.T) {
   343  	contents := `foo()
   344  
   345  # buildifier: disable=depset-iteration
   346  for x in depset([1, 2, 3]):
   347      print(x)  # buildozer: disable=print
   348  
   349  for y in "foobar":  # buildozer: disable=string-iteration
   350      # buildifier: disable=no-effect
   351      y
   352  
   353  # buildifier: disable=duplicated-name-2
   354  cc_library(
   355     name = "foo",  # buildifier: disable=duplicated-name-1
   356  )
   357  
   358  # buildifier: disable=skylark-comment
   359  # some comment mentioning skylark
   360  `
   361  
   362  	f, err := build.ParseBzl("file.bzl", []byte(contents))
   363  	if err != nil {
   364  		t.Fatalf("Parse error: %v", err)
   365  	}
   366  
   367  	tests := []struct {
   368  		start    int
   369  		end      int
   370  		category string
   371  	}{
   372  		{
   373  			start:    3,
   374  			end:      5,
   375  			category: "depset-iteration",
   376  		},
   377  		{
   378  			start:    5,
   379  			end:      5,
   380  			category: "print",
   381  		},
   382  		{
   383  			start:    7,
   384  			end:      7,
   385  			category: "string-iteration",
   386  		},
   387  		{
   388  			start:    8,
   389  			end:      9,
   390  			category: "no-effect",
   391  		},
   392  		{
   393  			start:    13,
   394  			end:      13,
   395  			category: "duplicated-name-1",
   396  		},
   397  		{
   398  			start:    11,
   399  			end:      14,
   400  			category: "duplicated-name-2",
   401  		},
   402  		{
   403  			start:    16,
   404  			end:      17,
   405  			category: "skylark-comment",
   406  		},
   407  	}
   408  
   409  	linesCount := strings.Count(contents, "\n")
   410  
   411  	for _, tc := range tests {
   412  		for line := 1; line <= linesCount; line++ {
   413  			disabled := DisabledWarning(f, line, tc.category)
   414  			shouldBeDisabled := line >= tc.start && line <= tc.end
   415  			if disabled != shouldBeDisabled {
   416  				t.Errorf("Wrong disabled status for the category %q, want %t, got %t", tc.category, shouldBeDisabled, disabled)
   417  			}
   418  		}
   419  	}
   420  }
   421  

View as plain text