[BACK]Return to autofix_test.go CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / pkgsrc / pkgtools / pkglint / files

File: [cvs.NetBSD.org] / pkgsrc / pkgtools / pkglint / files / Attic / autofix_test.go (download)

Revision 1.10, Wed Sep 5 17:56:22 2018 UTC (5 years, 7 months ago) by rillig
Branch: MAIN
CVS Tags: pkgsrc-2018Q3-base, pkgsrc-2018Q3
Changes since 1.9: +102 -0 lines

pkgtools/pkglint: update to 5.6.2

Changes since 5.6.1:

* Improved checks that depend on whether bsd.prefs.mk is included or
  not.

* Improved checks for tools, whether they may be used at load time
  or at run time.

* Improved tokenizer for shell commands. $| is not a variable but a
  dollar followed by a pipe.

* Warnings about SUBST context are now shown by default.

* A warning is shown when a SUBST block is declared for *-configure
  but the package has defined USE_CONFIGURE=no.

* Don't warn about USE_TOOLS:= ${USE_TOOLS:Ntool}.

* Don't warn about using the ?= operator in buildlink3.mk files before
  including bsd.prefs.mk (for some more variables, but not all).

* Report an error for packages from main pkgsrc that have a TODO or
  README file. Packages should be simple enough that they don't need
  a README file and ready for production so that they don't need a TODO.

* Lots of small bug fixes and new tests.

package main

import (
	"gopkg.in/check.v1"
	"os"
	"runtime"
	"strings"
)

func (s *Suite) Test_Autofix_ReplaceRegex(c *check.C) {
	t := s.Init(c)

	t.SetupCommandLine("--show-autofix")
	lines := t.SetupFileLines("Makefile",
		"line1",
		"line2",
		"line3")

	fix := lines[1].Autofix()
	fix.Warnf("Something's wrong here.")
	fix.ReplaceRegex(`.`, "X", -1)
	fix.Apply()
	SaveAutofixChanges(lines)

	c.Check(lines[1].raw[0].textnl, equals, "XXXXX\n")
	t.CheckFileLines("Makefile",
		"line1",
		"line2",
		"line3")
	t.CheckOutputLines(
		"WARN: ~/Makefile:2: Something's wrong here.",
		"AUTOFIX: ~/Makefile:2: Replacing \"l\" with \"X\".",
		"AUTOFIX: ~/Makefile:2: Replacing \"i\" with \"X\".",
		"AUTOFIX: ~/Makefile:2: Replacing \"n\" with \"X\".",
		"AUTOFIX: ~/Makefile:2: Replacing \"e\" with \"X\".",
		"AUTOFIX: ~/Makefile:2: Replacing \"2\" with \"X\".")
}

func (s *Suite) Test_Autofix_ReplaceRegex_with_autofix(c *check.C) {
	t := s.Init(c)

	t.SetupCommandLine("--autofix", "--source")
	lines := t.SetupFileLines("Makefile",
		"line1",
		"line2",
		"line3")

	fix := lines[1].Autofix()
	fix.Warnf("Something's wrong here.")
	fix.ReplaceRegex(`.`, "X", 3)
	fix.Apply()

	t.CheckOutputLines(
		"AUTOFIX: ~/Makefile:2: Replacing \"l\" with \"X\".",
		"AUTOFIX: ~/Makefile:2: Replacing \"i\" with \"X\".",
		"AUTOFIX: ~/Makefile:2: Replacing \"n\" with \"X\".",
		"-\tline2",
		"+\tXXXe2")

	fix.Warnf("Use Y instead of X.")
	fix.Replace("X", "Y")
	fix.Apply()

	t.CheckOutputLines(
		"",
		"AUTOFIX: ~/Makefile:2: Replacing \"X\" with \"Y\".",
		"-\tline2",
		"+\tYXXe2")

	SaveAutofixChanges(lines)

	t.CheckFileLines("Makefile",
		"line1",
		"YXXe2",
		"line3")
}

func (s *Suite) Test_Autofix_ReplaceRegex_with_show_autofix(c *check.C) {
	t := s.Init(c)

	t.SetupCommandLine("--show-autofix", "--source")
	lines := t.SetupFileLines("Makefile",
		"line1",
		"line2",
		"line3")

	fix := lines[1].Autofix()
	fix.Warnf("Something's wrong here.")
	fix.ReplaceRegex(`.`, "X", -1)
	fix.Apply()

	fix.Warnf("Use Y instead of X.")
	fix.Replace("X", "Y")
	fix.Apply()

	SaveAutofixChanges(lines)

	t.CheckOutputLines(
		"WARN: ~/Makefile:2: Something's wrong here.",
		"AUTOFIX: ~/Makefile:2: Replacing \"l\" with \"X\".",
		"AUTOFIX: ~/Makefile:2: Replacing \"i\" with \"X\".",
		"AUTOFIX: ~/Makefile:2: Replacing \"n\" with \"X\".",
		"AUTOFIX: ~/Makefile:2: Replacing \"e\" with \"X\".",
		"AUTOFIX: ~/Makefile:2: Replacing \"2\" with \"X\".",
		"-\tline2",
		"+\tXXXXX",
		"",
		"WARN: ~/Makefile:2: Use Y instead of X.",
		"AUTOFIX: ~/Makefile:2: Replacing \"X\" with \"Y\".",
		"-\tline2",
		"+\tYXXXX")
}

func (s *Suite) Test_autofix_MkLines(c *check.C) {
	t := s.Init(c)

	t.SetupCommandLine("--autofix")
	t.SetupFileLines("category/basename/Makefile",
		"line1 := value1",
		"line2 := value2",
		"line3 := value3")
	pkg := NewPackage(t.File("category/basename"))
	G.Pkg = pkg
	mklines := pkg.loadPackageMakefile()
	G.Pkg = nil

	fix := mklines.mklines[1].Autofix()
	fix.Warnf("Something's wrong here.")
	fix.ReplaceRegex(`...`, "XXX", -1)
	fix.Apply()

	fix = mklines.mklines[2].Autofix()
	fix.Warnf("Something's wrong here.")
	fix.ReplaceRegex(`...`, "XXX", 1)
	fix.Apply()

	SaveAutofixChanges(mklines.lines)

	t.CheckOutputLines(
		"AUTOFIX: ~/category/basename/Makefile:2: Replacing \"lin\" with \"XXX\".",
		"AUTOFIX: ~/category/basename/Makefile:2: Replacing \"e2 \" with \"XXX\".",
		"AUTOFIX: ~/category/basename/Makefile:2: Replacing \":= \" with \"XXX\".",
		"AUTOFIX: ~/category/basename/Makefile:2: Replacing \"val\" with \"XXX\".",
		"AUTOFIX: ~/category/basename/Makefile:2: Replacing \"ue2\" with \"XXX\".",
		"AUTOFIX: ~/category/basename/Makefile:3: Replacing \"lin\" with \"XXX\".")
	t.CheckFileLines("category/basename/Makefile",
		"line1 := value1",
		"XXXXXXXXXXXXXXX",
		"XXXe3 := value3")
}

func (s *Suite) Test_Autofix__multiple_modifications(c *check.C) {
	t := s.Init(c)

	t.SetupCommandLine("--show-autofix", "--explain")

	line := t.NewLine("fname", 1, "original")

	c.Check(line.autofix, check.IsNil)
	c.Check(line.raw, check.DeepEquals, t.NewRawLines(1, "original\n"))

	{
		fix := line.Autofix()
		fix.Warnf("Silent-Magic-Diagnostic")
		fix.ReplaceRegex(`(.)(.*)(.)`, "lriginao", 1) // XXX: the replacement should be "$3$2$1"
		fix.Apply()
	}

	c.Check(line.autofix, check.NotNil)
	c.Check(line.raw, check.DeepEquals, t.NewRawLines(1, "original\n", "lriginao\n"))
	t.CheckOutputLines(
		"AUTOFIX: fname:1: Replacing \"original\" with \"lriginao\".")

	{
		fix := line.Autofix()
		fix.Warnf("Silent-Magic-Diagnostic")
		fix.Replace("i", "u")
		fix.Apply()
	}

	c.Check(line.autofix, check.NotNil)
	c.Check(line.raw, check.DeepEquals, t.NewRawLines(1, "original\n", "lruginao\n"))
	c.Check(line.raw[0].textnl, equals, "lruginao\n")
	t.CheckOutputLines(
		"AUTOFIX: fname:1: Replacing \"i\" with \"u\".")

	{
		fix := line.Autofix()
		fix.Warnf("Silent-Magic-Diagnostic")
		fix.Replace("lruginao", "middle")
		fix.Apply()
	}

	c.Check(line.autofix, check.NotNil)
	c.Check(line.raw, check.DeepEquals, t.NewRawLines(1, "original\n", "middle\n"))
	c.Check(line.raw[0].textnl, equals, "middle\n")
	t.CheckOutputLines(
		"AUTOFIX: fname:1: Replacing \"lruginao\" with \"middle\".")

	{
		fix := line.Autofix()
		fix.Warnf("Silent-Magic-Diagnostic")
		fix.InsertBefore("before")
		fix.Apply()

		fix.Warnf("Silent-Magic-Diagnostic")
		fix.InsertBefore("between before and middle")
		fix.Apply()

		fix.Warnf("Silent-Magic-Diagnostic")
		fix.InsertAfter("between middle and after")
		fix.Apply()

		fix.Notef("This diagnostic is necessary for the following explanation.")
		fix.Explain(
			"When inserting multiple lines, Apply must be called in-between.",
			"Otherwise the changes are not described to the human reader.")
		fix.InsertAfter("after")
		fix.Apply()
	}

	c.Check(line.autofix.linesBefore, check.DeepEquals, []string{
		"before\n",
		"between before and middle\n"})
	c.Check(line.autofix.lines[0].textnl, equals, "middle\n")
	c.Check(line.autofix.linesAfter, deepEquals, []string{
		"between middle and after\n",
		"after\n"})
	t.CheckOutputLines(
		"AUTOFIX: fname:1: Inserting a line \"before\" before this line.",
		"AUTOFIX: fname:1: Inserting a line \"between before and middle\" before this line.",
		"AUTOFIX: fname:1: Inserting a line \"between middle and after\" after this line.",
		"NOTE: fname:1: This diagnostic is necessary for the following explanation.",
		"AUTOFIX: fname:1: Inserting a line \"after\" after this line.",
		"",
		"\tWhen inserting multiple lines, Apply must be called in-between.",
		"\tOtherwise the changes are not described to the human reader.",
		"")

	{
		fix := line.Autofix()
		fix.Warnf("Silent-Magic-Diagnostic")
		fix.Delete()
		fix.Apply()
	}

	c.Check(line.autofix.linesBefore, check.DeepEquals, []string{
		"before\n",
		"between before and middle\n"})
	c.Check(line.autofix.lines[0].textnl, equals, "")
	c.Check(line.autofix.linesAfter, deepEquals, []string{
		"between middle and after\n",
		"after\n"})
	t.CheckOutputLines(
		"AUTOFIX: fname:1: Deleting this line.")
}

func (s *Suite) Test_Autofix_show_source_code(c *check.C) {
	t := s.Init(c)

	t.SetupCommandLine("--show-autofix", "--source")
	mklines := t.SetupFileMkLines("Makefile",
		MkRcsID,
		"# before \\",
		"The old song \\",
		"after")
	line := mklines.lines[1]

	{
		fix := line.Autofix()
		fix.Warnf("Using \"old\" is deprecated.")
		fix.Replace("old", "new")
		fix.Apply()
	}

	t.CheckOutputLines(
		"WARN: ~/Makefile:2--4: Using \"old\" is deprecated.",
		"AUTOFIX: ~/Makefile:3: Replacing \"old\" with \"new\".",
		">\t# before \\",
		"-\tThe old song \\",
		"+\tThe new song \\",
		">\tafter")
}

func (s *Suite) Test_Autofix_InsertBefore(c *check.C) {
	t := s.Init(c)

	t.SetupCommandLine("--show-autofix", "--source")
	line := t.NewLine("Makefile", 30, "original")

	fix := line.Autofix()
	fix.Warnf("Dummy.")
	fix.InsertBefore("inserted")
	fix.Apply()

	t.CheckOutputLines(
		"WARN: Makefile:30: Dummy.",
		"AUTOFIX: Makefile:30: Inserting a line \"inserted\" before this line.",
		"+\tinserted",
		">\toriginal")
}

func (s *Suite) Test_Autofix_Delete(c *check.C) {
	t := s.Init(c)

	t.SetupCommandLine("--show-autofix", "--source")
	line := t.NewLine("Makefile", 30, "to be deleted")

	fix := line.Autofix()
	fix.Warnf("Dummy.")
	fix.Delete()
	fix.Apply()

	t.CheckOutputLines(
		"WARN: Makefile:30: Dummy.",
		"AUTOFIX: Makefile:30: Deleting this line.",
		"-\tto be deleted")
}

// Demonstrates that the --show-autofix option only shows those diagnostics
// that would be fixed.
func (s *Suite) Test_Autofix_suppress_unfixable_warnings(c *check.C) {
	t := s.Init(c)

	t.SetupCommandLine("--show-autofix", "--source")
	lines := t.NewLines("Makefile",
		"line1",
		"line2",
		"line3")

	lines[0].Warnf("This warning is not shown since it is not automatically fixed.")

	fix := lines[1].Autofix()
	fix.Warnf("Something's wrong here.")
	fix.ReplaceRegex(`.`, "X", -1)
	fix.Apply()

	fix.Warnf("The XXX marks are usually not fixed, use TODO instead.")
	fix.Replace("XXX", "TODO")
	fix.Apply()

	lines[2].Warnf("Neither is this warning shown.")

	t.CheckOutputLines(
		"WARN: Makefile:2: Something's wrong here.",
		"AUTOFIX: Makefile:2: Replacing \"l\" with \"X\".",
		"AUTOFIX: Makefile:2: Replacing \"i\" with \"X\".",
		"AUTOFIX: Makefile:2: Replacing \"n\" with \"X\".",
		"AUTOFIX: Makefile:2: Replacing \"e\" with \"X\".",
		"AUTOFIX: Makefile:2: Replacing \"2\" with \"X\".",
		"-\tline2",
		"+\tXXXXX",
		"",
		"WARN: Makefile:2: The XXX marks are usually not fixed, use TODO instead.",
		"AUTOFIX: Makefile:2: Replacing \"XXX\" with \"TODO\".",
		"-\tline2",
		"+\tTODOXX")
}

// If an Autofix doesn't do anything it must not log any diagnostics.
func (s *Suite) Test_Autofix_failed_replace(c *check.C) {
	t := s.Init(c)

	line := t.NewLine("Makefile", 14, "Original text")

	fix := line.Autofix()
	fix.Warnf("All-uppercase words should not be used at all.")
	fix.ReplaceRegex(`\b[A-Z]{3,}\b`, "---censored---", -1)
	fix.Apply()

	// No output since there was no all-uppercase word in the text.
	t.CheckOutputEmpty()
}

func (s *Suite) Test_SaveAutofixChanges(c *check.C) {
	t := s.Init(c)

	t.SetupCommandLine("--autofix")
	lines := t.SetupFileLines("DESCR",
		"Line 1",
		"Line 2")

	fix := lines[0].Autofix()
	fix.Warnf("Dummy warning.")
	fix.Replace("X", "Y")
	fix.Apply()

	// Since nothing has been effectively changed,
	// nothing needs to be saved.
	SaveAutofixChanges(lines)

	// And therefore, no AUTOFIX action must appear in the log.
	t.CheckOutputEmpty()
}

func (s *Suite) Test_Autofix_CustomFix(c *check.C) {
	t := s.Init(c)

	lines := t.NewLines("Makefile",
		"line1",
		"line2",
		"line3")

	doFix := func(line Line) {
		fix := line.Autofix()
		fix.Warnf("Please write in ALL-UPPERCASE.")
		fix.Custom(func(printAutofix, autofix bool) {
			fix.Describef(int(line.firstLine), "Converting to uppercase")
			if printAutofix || autofix {
				line.Text = strings.ToUpper(line.Text)
			}
		})
		fix.Apply()
	}

	doFix(lines[0])

	t.CheckOutputLines(
		"WARN: Makefile:1: Please write in ALL-UPPERCASE.")

	t.SetupCommandLine("--show-autofix")

	doFix(lines[1])

	t.CheckOutputLines(
		"WARN: Makefile:2: Please write in ALL-UPPERCASE.",
		"AUTOFIX: Makefile:2: Converting to uppercase")
	c.Check(lines[1].Text, equals, "LINE2")

	t.SetupCommandLine("--autofix")

	doFix(lines[2])

	t.CheckOutputLines(
		"AUTOFIX: Makefile:3: Converting to uppercase")
	c.Check(lines[2].Text, equals, "LINE3")
}

func (s *Suite) Test_Autofix_Explain(c *check.C) {
	t := s.Init(c)

	line := t.NewLine("Makefile", 74, "line1")

	fix := line.Autofix()
	fix.Warnf("Please write row instead of line.")
	fix.Replace("line", "row")
	fix.Explain("Explanation")
	fix.Apply()

	t.CheckOutputLines(
		"WARN: Makefile:74: Please write row instead of line.")
	c.Check(G.explanationsAvailable, equals, true)
}

// Since the diagnostic doesn't contain the string "few", nothing happens.
func (s *Suite) Test_Autofix__skip(c *check.C) {
	t := s.Init(c)

	t.SetupCommandLine("--only", "few", "--autofix")

	lines := t.SetupFileLines("fname",
		"111 222 333 444 555")

	fix := lines[0].Autofix()
	fix.Warnf("Many.")
	fix.Explain(
		"Explanation.")
	fix.Replace("111", "___")
	fix.ReplaceAfter(" ", "222", "___")
	fix.ReplaceRegex(`\d+`, "___", 1)
	fix.InsertBefore("before")
	fix.InsertAfter("after")
	fix.Delete()
	fix.Custom(func(printAutofix, autofix bool) {})
	fix.Realign(dummyMkLine, 32)
	fix.Apply()

	SaveAutofixChanges(lines)

	t.CheckOutputEmpty()
	t.CheckFileLines("fname",
		"111 222 333 444 555")
	c.Check(lines[0].raw[0].textnl, equals, "111 222 333 444 555\n")
}

func (s *Suite) Test_Autofix_Apply__panic(c *check.C) {
	t := s.Init(c)

	line := t.NewLine("filename", 123, "text")

	c.Assert(func() {
		fix := line.Autofix()
		fix.Apply()
	}, check.Panics, "Each autofix must have a diagnostic.")

	c.Assert(func() {
		fix := line.Autofix()
		fix.Replace("from", "to")
		fix.Apply()
	}, check.Panics, "Autofix: The diagnostic must be given before the action.")

	c.Assert(func() {
		fix := line.Autofix()
		fix.Warnf("Warning without period")
		fix.Apply()
	}, check.Panics, "Autofix: format \"Warning without period\" must end with a period.")
}

func (s *Suite) Test_Autofix_Apply__file_removed(c *check.C) {
	t := s.Init(c)

	t.SetupCommandLine("--autofix")
	lines := t.SetupFileLines("subdir/file.txt",
		"line 1")
	os.RemoveAll(t.File("subdir"))

	fix := lines[0].Autofix()
	fix.Warnf("Should start with an uppercase letter.")
	fix.Replace("line", "Line")
	fix.Apply()

	SaveAutofixChanges(lines)

	c.Check(t.Output(), check.Matches, ""+
		"AUTOFIX: ~/subdir/file.txt:1: Replacing \"line\" with \"Line\".\n"+
		"ERROR: ~/subdir/file.txt.pkglint.tmp: Cannot write: .*\n")
}

func (s *Suite) Test_Autofix_Apply__file_busy_Windows(c *check.C) {
	t := s.Init(c)

	if runtime.GOOS != "windows" {
		return
	}

	t.SetupCommandLine("--autofix")
	lines := t.SetupFileLines("subdir/file.txt",
		"line 1")

	// As long as the file is kept open, it cannot be overwritten or deleted.
	openFile, err := os.OpenFile(t.File("subdir/file.txt"), 0, 0666)
	defer openFile.Close()
	c.Check(err, check.IsNil)

	fix := lines[0].Autofix()
	fix.Warnf("Should start with an uppercase letter.")
	fix.Replace("line", "Line")
	fix.Apply()

	SaveAutofixChanges(lines)

	c.Check(t.Output(), check.Matches, ""+
		"AUTOFIX: ~/subdir/file.txt:1: Replacing \"line\" with \"Line\".\n"+
		"ERROR: ~/subdir/file.txt.pkglint.tmp: Cannot overwrite with auto-fixed content: .*\n")
}

// This test tests the highly unlikely situation in which a file is loaded
// by pkglint, and just before writing the autofixed content back, another
// process takes the file and replaces it with a directory of the same name.
//
// 100% code coverage sometimes requires creativity. :)
func (s *Suite) Test_Autofix_Apply__file_converted_to_directory(c *check.C) {
	t := s.Init(c)

	t.SetupCommandLine("--autofix")
	lines := t.SetupFileLines("file.txt",
		"line 1")

	c.Check(os.RemoveAll(t.File("file.txt")), check.IsNil)
	c.Check(os.MkdirAll(t.File("file.txt"), 0777), check.IsNil)

	fix := lines[0].Autofix()
	fix.Warnf("Should start with an uppercase letter.")
	fix.Replace("line", "Line")
	fix.Apply()

	SaveAutofixChanges(lines)

	c.Check(t.Output(), check.Matches, ""+
		"AUTOFIX: ~/file.txt:1: Replacing \"line\" with \"Line\".\n"+
		"ERROR: ~/file.txt.pkglint.tmp: Cannot overwrite with auto-fixed content: .*\n")
}