pkgsrc/pkgtools/pkglint/files/logging_test.go
rillig 1d1d611496 pkgtools/pkglint: update to 21.2.1
Changes since 21.2.0:

Files whose names ends in '~' are ignored by pkglint since they are
ignored by CVS as well.

Variables with name BUILDLINK_TRANSFORM.* may contain '-Wl,-rpath,'
directly in commands of the form 'rm:*', just like their counterpart
BUILDLINK_TRANSFORM without a package name in the variable name.

Several new tests.
2021-06-25 14:15:00 +00:00

1406 lines
39 KiB
Go

package pkglint
import (
"gopkg.in/check.v1"
"netbsd.org/pkglint/histogram"
"strings"
)
func (s *Suite) Test_Logger_Explain__only(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--only", "interesting", "--explain")
line := t.NewLine("Makefile", 27, "The old song")
// Neither the warning nor the corresponding explanation are logged.
line.Warnf("Filtered warning.")
line.Explain("Explanation for the above warning.")
line.Notef("What an interesting line.")
line.Explain("This explanation is logged.")
t.CheckOutputLines(
"NOTE: Makefile:27: What an interesting line.",
"",
"\tThis explanation is logged.",
"")
}
func (s *Suite) Test_Logger_Explain__show_autofix(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--explain", "--show-autofix")
line := t.NewLine("Makefile", 27, "The old song")
line.Warnf("Warning without fix.")
line.Explain(
"Explanation for warning without fix.")
fix := line.Autofix()
fix.Warnf("Warning with fix.")
fix.Explain(
"Explanation for warning with fix.")
fix.Replace("old", "new")
fix.Apply()
// Since the warning without fix doesn't fix anything, it is filtered out.
// So is the corresponding explanation.
t.CheckOutputLines(
"WARN: Makefile:27: Warning with fix.",
"AUTOFIX: Makefile:27: Replacing \"old\" with \"new\".",
"",
"\tExplanation for warning with fix.",
"")
}
func (s *Suite) Test_Logger_Explain__show_autofix_and_source(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--explain", "--show-autofix", "--source")
line := t.NewLine("Makefile", 27, "The old song")
line.Warnf("Warning without fix.")
line.Explain(
"Explanation for warning without fix.")
fix := line.Autofix()
fix.Warnf("Warning with fix.")
fix.Explain(
"Explanation for warning with fix.")
fix.Replace("old", "new")
fix.Apply()
// Since the warning without fix doesn't fix anything, it is filtered out.
// So is the corresponding explanation.
t.CheckOutputLines(
"WARN: Makefile:27: Warning with fix.",
"AUTOFIX: Makefile:27: Replacing \"old\" with \"new\".",
"-\tThe old song",
"+\tThe new song",
"",
"\tExplanation for warning with fix.",
"")
}
// When the --autofix option is given, the warnings are not shown, therefore it doesn't
// make sense to show the explanation for the warning.
func (s *Suite) Test_Logger_Explain__autofix_and_source(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--explain", "--autofix", "--source")
line := t.NewLine("Makefile", 27, "The old song")
line.Warnf("Warning without fix.")
line.Explain(
"Explanation for warning without fix.")
fix := line.Autofix()
fix.Warnf("Warning with fix.")
fix.Explain(
"Explanation for warning with fix.")
fix.Replace("old", "new")
fix.Apply()
// Since the warning without fix doesn't fix anything, it is filtered out.
// So is the corresponding explanation.
t.CheckOutputLines(
"AUTOFIX: Makefile:27: Replacing \"old\" with \"new\".",
"-\tThe old song",
"+\tThe new song")
}
// When an explanation consists of multiple paragraphs, it contains some empty lines.
// When printing these lines, there is no need to write the tab that is used for indenting
// the normal lines.
//
// Since pkglint likes to complain about trailing whitespace, it should not generate it itself.
func (s *Suite) Test_Logger_Explain__empty_lines(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--explain")
line := t.NewLine("Makefile", 27, "The old song")
line.Warnf("A normal warning.")
line.Explain(
"Paragraph 1 of the explanation.",
"",
"Paragraph 2 of the explanation.")
t.CheckOutputLines(
"WARN: Makefile:27: A normal warning.",
"",
"\tParagraph 1 of the explanation.",
"",
"\tParagraph 2 of the explanation.",
"")
}
// In an explanation, it can happen that the pkgsrc directory is mentioned.
// While pkgsrc does not support either PKGSRCDIR or PREFIX or really any
// other directory name to contain spaces, during pkglint development this
// may happen because the pkgsrc root is in the temporary directory.
//
// In this situation, the ~ placeholder must still be properly substituted.
func (s *Suite) Test_Logger_Explain__line_wrapped_temporary_directory(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--explain")
filename := t.File("filename.mk")
mkline := t.NewMkLine(filename, 123, "")
mkline.Notef("Just a note to get the below explanation.")
G.Logger.Explain(
sprintf("%[1]s %[1]s %[1]s %[1]s %[1]s %[1]q", filename))
t.CheckOutputLinesIgnoreSpace(
"NOTE: ~/filename.mk:123: Just a note to get the below explanation.",
"",
"\t~/filename.mk",
"\t~/filename.mk",
"\t~/filename.mk",
"\t~/filename.mk",
"\t~/filename.mk",
"\t\"~/filename.mk\"",
"")
}
// Diag filters duplicate messages, unlike Logf.
func (s *Suite) Test_Logger_Diag__duplicates(c *check.C) {
t := s.Init(c)
var sw strings.Builder
logger := Logger{out: NewSeparatorWriter(&sw)}
line := t.NewLine("filename", 3, "Text")
logger.Diag(line, Error, "Blue must be %s.", "orange")
logger.Diag(line, Error, "Blue must be %s.", "orange")
t.CheckEquals(sw.String(), ""+
"ERROR: filename:3: Blue must be orange.\n")
}
// Explanations are associated with their diagnostics. Therefore, when one
// of them is suppressed, the other is suppressed, too.
func (s *Suite) Test_Logger_Diag__explanation(c *check.C) {
t := s.Init(c)
var sw strings.Builder
logger := Logger{out: NewSeparatorWriter(&sw)}
logger.Opts.Explain = true
line := t.NewLine("filename", 3, "Text")
logger.Diag(line, Error, "Blue must be %s.", "orange")
logger.Explain(
"The colors have changed.")
logger.Diag(line, Error, "Blue must be %s.", "orange")
logger.Explain(
"The colors have changed.")
// Even when the text of the explanation is not the same, it is still
// suppressed since it belongs to the diagnostic.
logger.Diag(line, Error, "Blue must be %s.", "orange")
logger.Explain(
"The colors have further changed.")
t.CheckEquals(sw.String(), ""+
"ERROR: filename:3: Blue must be orange.\n"+
"\n"+
"\tThe colors have changed.\n"+
"\n")
}
func (s *Suite) Test_Logger_Diag__show_source(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--show-autofix", "--source")
line := t.NewLine("filename", 123, "text")
fix := line.Autofix()
fix.Notef("Diagnostics can show the differences in autofix mode.")
fix.InsertAbove("new line above")
fix.InsertBelow("new line below")
fix.Apply()
t.CheckOutputLines(
"NOTE: filename:123: Diagnostics can show the differences in autofix mode.",
"AUTOFIX: filename:123: Inserting a line \"new line above\" above this line.",
"AUTOFIX: filename:123: Inserting a line \"new line below\" below this line.",
"+\tnew line above",
">\ttext",
"+\tnew line below")
}
func (s *Suite) Test_Logger_Diag__show_source_with_whole_file(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--source")
line := NewLineWhole("filename")
line.Warnf("This line does not have any RawLine attached.")
t.CheckOutputLines(
"WARN: filename: This line does not have any RawLine attached.")
}
// Ensures that when two packages produce a warning in the same file, both the
// warning and the corresponding source code are logged only once.
func (s *Suite) Test_Logger_Diag__source_duplicates(c *check.C) {
t := s.Init(c)
// Up to pkglint 19.3.10, this variable had been reset during
// command line parsing. In 19.3.11 the command line option has
// been removed, therefore it must be reset manually.
G.Logger.verbose = false
t.SetUpPkgsrc()
t.CreateFileLines("category/dependency/patches/patch-aa",
CvsID,
"",
"--- old file",
"+++ new file",
"@@ -1,1 +1,1 @@",
"-old line",
"+new line")
t.SetUpPackage("category/package1",
"PATCHDIR=\t../../category/dependency/patches")
t.SetUpPackage("category/package2",
"PATCHDIR=\t../../category/dependency/patches")
t.Main("--source", "-Wall", "category/package1", "category/package2")
t.CheckOutputLines(
"ERROR: ~/category/package1/distinfo: "+
"Patch \"../../category/dependency/patches/patch-aa\" is not recorded. "+
"Run \""+confMake+" makepatchsum\".",
"",
">\t--- old file",
"ERROR: ~/category/dependency/patches/patch-aa:3: "+
"Each patch must be documented.",
"",
"ERROR: ~/category/package2/distinfo: "+
"Patch \"../../category/dependency/patches/patch-aa\" is not recorded. "+
"Run \""+confMake+" makepatchsum\".",
"",
"3 errors found.",
t.Shquote("(Run \"pkglint -e --source -Wall %s %s\" to show explanations.)",
"category/package1", "category/package2"))
}
func (s *Suite) Test_Logger_FirstTime__not_verbose(c *check.C) {
t := s.Init(c)
G.Logger.verbose = false // as in a realistic run
t.CheckEquals(G.Logger.FirstTime("filename", "123", "Message."), true)
t.CheckEquals(G.Logger.FirstTime("filename", "123", "Message."), false)
t.CheckEquals(G.Logger.FirstTime("filename", "124", "Message."), true)
t.CheckEquals(G.Logger.FirstTime("filename", "124", "Message."), false)
t.CheckEquals(G.Logger.FirstTime("filename", "124", "Message."), false)
}
func (s *Suite) Test_Logger_Relevant(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine(nil...)
t.CheckEquals(G.Logger.Relevant("Options should not contain whitespace."), true)
t.CheckEquals(G.Logger.suppressDiag, false)
t.CheckEquals(G.Logger.suppressExpl, false) // XXX: Why not true?
t.SetUpCommandLine("--only", "whitespace")
t.CheckEquals(G.Logger.Relevant("Options should not contain whitespace."), true)
t.CheckEquals(G.Logger.suppressDiag, false)
t.CheckEquals(G.Logger.suppressExpl, false) // XXX: Why not true?
t.CheckEquals(G.Logger.Relevant("Options should not contain space."), false)
t.CheckEquals(G.Logger.suppressDiag, true)
t.CheckEquals(G.Logger.suppressExpl, true)
t.SetUpCommandLine("--explain")
t.CheckEquals(G.Logger.Relevant("Options should not contain whitespace."), true)
t.CheckEquals(G.Logger.suppressDiag, false)
t.CheckEquals(G.Logger.suppressExpl, false)
t.CheckEquals(G.Logger.Relevant("Options should not contain space."), true)
t.CheckEquals(G.Logger.suppressDiag, false)
t.CheckEquals(G.Logger.suppressExpl, false)
}
func (s *Suite) Test_Logger_shallBeLogged(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine( /* none */ )
t.CheckEquals(G.Logger.shallBeLogged("Options should not contain whitespace."), true)
t.SetUpCommandLine("--only", "whitespace")
t.CheckEquals(G.Logger.shallBeLogged("Options should not contain whitespace."), true)
t.CheckEquals(G.Logger.shallBeLogged("Options should not contain space."), false)
t.SetUpCommandLine( /* none again */ )
t.CheckEquals(G.Logger.shallBeLogged("Options should not contain whitespace."), true)
t.CheckEquals(G.Logger.shallBeLogged("Options should not contain space."), true)
}
// Since the --source option generates multi-line diagnostics,
// they are separated by an empty line.
//
// Whether the quoted source code is written above or below the
// diagnostics depends on the --show-autofix and --autofix options.
// When any of them is given, the general rule is given first, followed
// by a description of the fix ("replacing A with B"), finally followed
// by the actual changes to the code.
//
// In default mode, without any autofix options, the usual order is
// to first show the code and then show the diagnostic. This allows
// the diagnostics to underline the relevant part of the source code
// and reminds of the squiggly line used for spellchecking.
func (s *Suite) Test_Logger_writeSource__separator(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--source")
lines := t.SetUpFileLines("DESCR",
"The first line",
"The second line",
"The third line",
"The fourth line")
fix := lines.Lines[1].Autofix()
fix.Warnf("Using \"second\" is deprecated.")
fix.Replace("second", "silver medal")
fix.Apply()
lines.Lines[2].Warnf("Dummy warning.")
fix = lines.Lines[2].Autofix()
fix.Warnf("Using \"third\" is deprecated.")
fix.Replace("third", "bronze medal")
fix.Apply()
lines.Lines[3].Warnf("No autofix, just a warning.")
t.CheckOutputLines(
">\tThe second line",
"WARN: ~/DESCR:2: Using \"second\" is deprecated.",
"",
">\tThe third line",
"WARN: ~/DESCR:3: Dummy warning.",
"WARN: ~/DESCR:3: Using \"third\" is deprecated.",
"",
">\tThe fourth line",
"WARN: ~/DESCR:4: No autofix, just a warning.")
}
func (s *Suite) Test_Logger_writeSource__with_explanation(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--source", "--explain")
lines := t.SetUpFileLines("DESCR",
"The first line",
"The second line",
"The third line")
fix := lines.Lines[1].Autofix()
fix.Warnf("Using \"second\" is deprecated.")
fix.Explain("Explanation 1.")
fix.Replace("second", "silver medal")
fix.Apply()
lines.Lines[2].Warnf("Dummy warning.")
fix = lines.Lines[2].Autofix()
fix.Warnf("Using \"third\" is deprecated.")
fix.Explain("Explanation 2.")
fix.Replace("third", "bronze medal")
fix.Apply()
t.CheckOutputLines(
">\tThe second line",
"WARN: ~/DESCR:2: Using \"second\" is deprecated.",
"",
"\tExplanation 1.",
"",
">\tThe third line",
"WARN: ~/DESCR:3: Dummy warning.",
"WARN: ~/DESCR:3: Using \"third\" is deprecated.",
"",
"\tExplanation 2.",
"")
}
// In general, it is not necessary to repeat the source code for a line
// if there are several diagnostics for the same line. In this case though,
// there is an explanation between the diagnostics, and because it may get
// quite long, it's better to repeat the source code once again.
func (s *Suite) Test_Logger_writeSource__with_explanation_in_same_line(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--source", "--explain")
lines := t.SetUpFileLines("DESCR",
"The first line")
fix := lines.Lines[0].Autofix()
fix.Warnf("Using \"The\" is deprecated.")
fix.Explain("Explanation 1.")
fix.Replace("The", "A")
fix.Apply()
fix.Warnf("Using \"first\" is deprecated.")
fix.Explain("Explanation 2.")
fix.Replace("first", "1st")
fix.Apply()
t.CheckOutputLines(
">\tThe first line",
"WARN: ~/DESCR:1: Using \"The\" is deprecated.",
"",
"\tExplanation 1.",
"",
">\tThe first line",
"WARN: ~/DESCR:1: Using \"first\" is deprecated.",
"",
"\tExplanation 2.",
"")
}
// When there is no explanation after the first diagnostic, it is not
// necessary to repeat the source code again for the second diagnostic.
func (s *Suite) Test_Logger_writeSource__without_explanation_in_same_line(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--source", "--explain")
lines := t.SetUpFileLines("DESCR",
"The first line")
fix := lines.Lines[0].Autofix()
fix.Warnf("Using \"The\" is deprecated.")
fix.Replace("The", "A")
fix.Apply()
fix.Warnf("Using \"first\" is deprecated.")
fix.Explain("Explanation 2.")
fix.Replace("first", "1st")
fix.Apply()
t.CheckOutputLines(
">\tThe first line",
"WARN: ~/DESCR:1: Using \"The\" is deprecated.",
"WARN: ~/DESCR:1: Using \"first\" is deprecated.",
"",
"\tExplanation 2.",
"")
}
// When the --show-autofix option is given, the warning is shown first,
// without the affected source, even if the --source option is also given.
// This is because the original and the modified source are shown after
// the "Replacing" message. Since these are shown in diff style, they
// must be kept together. And since the "+" line must be below the "Replacing"
// line, this order of lines seems to be the most intuitive.
func (s *Suite) Test_Logger_writeSource__separator_show_autofix(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--source", "--show-autofix")
lines := t.SetUpFileLines("DESCR",
"The first line",
"The second line",
"The third line",
"The fourth line")
fix := lines.Lines[1].Autofix()
fix.Warnf("Using \"second\" is deprecated.")
fix.Replace("second", "silver medal")
fix.Apply()
lines.Lines[2].Warnf("Dummy warning.")
fix = lines.Lines[2].Autofix()
fix.Warnf("Using \"third\" is deprecated.")
fix.Replace("third", "bronze medal")
fix.Apply()
lines.Lines[3].Warnf("No autofix, just a warning.")
t.CheckOutputLines(
"WARN: ~/DESCR:2: Using \"second\" is deprecated.",
"AUTOFIX: ~/DESCR:2: Replacing \"second\" with \"silver medal\".",
"-\tThe second line",
"+\tThe silver medal line",
"",
"WARN: ~/DESCR:3: Using \"third\" is deprecated.",
"AUTOFIX: ~/DESCR:3: Replacing \"third\" with \"bronze medal\".",
"-\tThe third line",
"+\tThe bronze medal line")
}
func (s *Suite) Test_Logger_writeSource__separator_show_autofix_with_explanation(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--source", "--show-autofix", "--explain")
lines := t.SetUpFileLines("DESCR",
"The first line",
"The second line",
"The third line",
"The fourth line")
fix := lines.Lines[1].Autofix()
fix.Warnf("Using \"second\" is deprecated.")
fix.Explain("Explanation 1.")
fix.Replace("second", "silver medal")
fix.Apply()
lines.Lines[2].Warnf("Dummy warning.")
fix = lines.Lines[2].Autofix()
fix.Warnf("Using \"third\" is deprecated.")
fix.Explain("Explanation 2.")
fix.Replace("third", "bronze medal")
fix.Apply()
lines.Lines[3].Warnf("No autofix, just a warning.")
t.CheckOutputLines(
"WARN: ~/DESCR:2: Using \"second\" is deprecated.",
"AUTOFIX: ~/DESCR:2: Replacing \"second\" with \"silver medal\".",
"-\tThe second line",
"+\tThe silver medal line",
"",
"\tExplanation 1.",
"",
"WARN: ~/DESCR:3: Using \"third\" is deprecated.",
"AUTOFIX: ~/DESCR:3: Replacing \"third\" with \"bronze medal\".",
"-\tThe third line",
"+\tThe bronze medal line",
"",
"\tExplanation 2.",
"")
}
// Fatal errors are not specific to a single line, therefore they only
// take a filename as argument.
// The --show-autofix and --source options have no effect on fatal errors.
func (s *Suite) Test_Logger_writeSource__fatal_with_show_autofix(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--source", "--show-autofix")
t.ExpectFatal(
func() { G.Logger.TechFatalf("DESCR", "Fatal.") },
"FATAL: DESCR: Fatal.")
}
// See Test__show_source_separator_show_autofix for the ordering of the
// output lines.
//
// TODO: Giving the diagnostics again would be useful, but the warning and
// error counters should not be affected, as well as the exitcode.
func (s *Suite) Test_Logger_writeSource__separator_autofix(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--source", "--autofix")
lines := t.SetUpFileLines("DESCR",
"The first line",
"The second line",
"The third line",
"The fourth line")
fix := lines.Lines[1].Autofix()
fix.Warnf("Using \"second\" is deprecated.")
fix.Replace("second", "silver medal")
fix.Apply()
lines.Lines[2].Warnf("Dummy warning.")
fix = lines.Lines[2].Autofix()
fix.Warnf("Using \"third\" is deprecated.")
fix.Replace("third", "bronze medal")
fix.Apply()
lines.Lines[3].Warnf("No autofix, just a warning.")
t.CheckOutputLines(
"AUTOFIX: ~/DESCR:2: Replacing \"second\" with \"silver medal\".",
"-\tThe second line",
"+\tThe silver medal line",
"",
"AUTOFIX: ~/DESCR:3: Replacing \"third\" with \"bronze medal\".",
"-\tThe third line",
"+\tThe bronze medal line")
}
func (s *Suite) Test_Logger_writeSource__first_warn_then_autofix(c *check.C) {
t := s.Init(c)
test := func(diagnostics ...string) {
lines := t.SetUpFileLines("DESCR",
"The first line",
"The second line")
line := lines.Lines[0]
line.Warnf("Warning.")
fix := line.Autofix()
fix.Warnf("Autofix.")
fix.Replace("first", "upper")
fix.Apply()
fix = lines.Lines[1].Autofix()
fix.Warnf("Autofix.")
fix.Replace("second", "last")
fix.Apply()
t.CheckOutput(diagnostics)
}
t.SetUpCommandLine("--source")
// The warning reports the unmodified source text of the affected line.
// Later, the autofix modifies that same line, but the modification is
// not reported.
// Luckily, this behavior is consistent with the one in line 2, which
// also only reports the original source text.
test(
">\tThe first line",
"WARN: ~/DESCR:1: Warning.",
"WARN: ~/DESCR:1: Autofix.",
"",
">\tThe second line",
"WARN: ~/DESCR:2: Autofix.")
t.SetUpCommandLine("--source", "--show-autofix")
test(
"WARN: ~/DESCR:1: Autofix.",
"AUTOFIX: ~/DESCR:1: Replacing \"first\" with \"upper\".",
"-\tThe first line",
"+\tThe upper line",
"",
"WARN: ~/DESCR:2: Autofix.",
"AUTOFIX: ~/DESCR:2: Replacing \"second\" with \"last\".",
"-\tThe second line",
"+\tThe last line")
}
func (s *Suite) Test_Logger_writeDiff(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--show-autofix")
line := t.NewLine("filename", 123, "before")
fix := line.Autofix()
fix.Silent()
fix.Replace("before", "after")
fix.Apply()
G.Logger.writeDiff(line)
// The diff lines are indented with a tab so that the indentation
// from the actual lines is properly represented in the output.
// If a space had been used here instead of the tab, the output
// would become garbled.
t.CheckOutputLines(
"AUTOFIX: filename:123: Replacing \"before\" with \"after\".",
"-\tbefore",
"+\tafter")
}
func (s *Suite) Test_Logger_writeLine(c *check.C) {
t := s.Init(c)
G.Logger.writeLine("> ", "\u0007\u00FC text")
t.CheckOutputLines(
"> <U+0007><U+00FC> text")
}
func (s *Suite) Test_Logger_IsAutofix__default(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("-Wall")
t.CheckEquals(G.Logger.IsAutofix(), false)
}
func (s *Suite) Test_Logger_IsAutofix__show_autofix(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("-Wall", "--show-autofix")
t.CheckEquals(G.Logger.IsAutofix(), true)
}
func (s *Suite) Test_Logger_IsAutofix__autofix(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("-Wall", "--autofix")
t.CheckEquals(G.Logger.IsAutofix(), true)
}
// Calling Logf without further preparation just logs the message.
// Suppressing duplicate messages or filtering messages happens
// in other methods of the Logger, namely Relevant, FirstTime, Diag.
func (s *Suite) Test_Logger_Logf(c *check.C) {
t := s.Init(c)
var sw strings.Builder
logger := Logger{out: NewSeparatorWriter(&sw)}
logger.Logf(Error, "filename", "3", "Blue must be %s.", "Blue must be orange.")
t.CheckEquals(sw.String(), ""+
"ERROR: filename:3: Blue must be orange.\n")
}
// Logf doesn't filter duplicates, but Diag does.
func (s *Suite) Test_Logger_Logf__duplicates(c *check.C) {
t := s.Init(c)
var sw strings.Builder
logger := Logger{out: NewSeparatorWriter(&sw)}
logger.Logf(Error, "filename", "3", "Blue must be %s.", "Blue must be orange.")
logger.Logf(Error, "filename", "3", "Blue must be %s.", "Blue must be orange.")
t.CheckEquals(sw.String(), ""+
"ERROR: filename:3: Blue must be orange.\n"+
"ERROR: filename:3: Blue must be orange.\n")
}
// Ensure that suppressing a diagnostic doesn't influence later calls to Logf.
func (s *Suite) Test_Logger_Logf__mixed_with_Diag(c *check.C) {
t := s.Init(c)
var sw strings.Builder
logger := Logger{out: NewSeparatorWriter(&sw)}
line := t.NewLine("filename", 3, "Text")
logger.Logf(Error, "filename", "3", "Logf output 1.", "Logf output 1.")
logger.Diag(line, Error, "Diag %s.", "1")
logger.Logf(Error, "filename", "3", "Logf output 2.", "Logf output 2.")
logger.Diag(line, Error, "Diag %s.", "1") // Duplicate, therefore suppressed
logger.Logf(Error, "filename", "3", "Logf output 3.", "Logf output 3.")
t.CheckEquals(sw.String(), ""+
"ERROR: filename:3: Logf output 1.\n"+
"ERROR: filename:3: Diag 1.\n"+
"ERROR: filename:3: Logf output 2.\n"+
"ERROR: filename:3: Logf output 3.\n")
}
func (s *Suite) Test_Logger_Logf__production(c *check.C) {
t := s.Init(c)
var sw strings.Builder
logger := Logger{out: NewSeparatorWriter(&sw)}
// In production mode, the checks for the diagnostic messages are
// turned off, for performance reasons. The unit tests provide
// enough coverage.
G.Testing = false
logger.Logf(Error, "filename", "3", "diagnostic", "message")
t.CheckEquals(sw.String(), ""+
"ERROR: filename:3: message\n")
}
func (s *Suite) Test_Logger_Logf__profiling(c *check.C) {
t := s.Init(c)
line := t.NewLine("filename", 123, "text")
G.Profiling = true
G.Logger.histo = histogram.New()
line.Warnf("Warning.")
G.Logger.histo.PrintStats(G.Logger.out.out, "loghisto", -1)
t.CheckOutputLines(
"WARN: filename:123: Warning.",
"loghisto 1 Warning.")
}
func (s *Suite) Test_Logger_Logf__profiling_autofix(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--show-autofix", "--source", "--explain")
line := t.NewLine("filename", 123, "text")
G.Profiling = true
G.Logger.histo = histogram.New()
fix := line.Autofix()
fix.Notef("Autofix note.")
fix.Explain(
"Autofix explanation.")
fix.Replace("text", "replacement")
fix.Apply()
// The AUTOFIX line is not counted in the histogram although
// it uses the same code path as the other messages.
G.Logger.histo.PrintStats(G.Logger.out.out, "loghisto", -1)
t.CheckOutputLines(
"NOTE: filename:123: Autofix note.",
"AUTOFIX: filename:123: Replacing \"text\" with \"replacement\".",
"-\ttext",
"+\treplacement",
"",
"\tAutofix explanation.",
"",
"loghisto 1 Autofix note.")
}
// In rare cases, the explanations for the same warning may differ
// when they appear in different contexts. In such a case, if the
// warning is suppressed, the explanation must not appear on its own.
//
// An example of this was (until November 2018) DESTDIR in the check
// for absolute pathnames.
func (s *Suite) Test_Logger_Logf__duplicate_messages(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--explain")
G.Logger.verbose = false
line := t.NewLine("README.txt", 123, "text")
// Is logged because it is the first appearance of this warning.
line.Warnf("The warning.")
line.Explain("Explanation 1")
// Is suppressed because the warning is the same as above and LogVerbose
// has been set to false for this test.
line.Warnf("The warning.")
line.Explain("Explanation 2")
t.CheckOutputLines(
"WARN: README.txt:123: The warning.",
"",
"\tExplanation 1",
"")
}
func (s *Suite) Test_Logger_Logf__duplicate_explanations(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--explain")
line := t.NewLine("README.txt", 123, "text")
// In rare cases, different diagnostics may have the same explanation.
line.Warnf("Warning 1.")
line.Explain("Explanation")
line.Warnf("Warning 2.")
line.Explain("Explanation") // Is suppressed.
t.CheckOutputLines(
"WARN: README.txt:123: Warning 1.",
"",
"\tExplanation",
"",
"WARN: README.txt:123: Warning 2.")
}
func (s *Suite) Test_Logger_Logf__gcc_format(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--gcc-output-format")
logger := &G.Logger
logger.Logf(Note, "filename", "123", "Both filename and line number.", "Both filename and line number.")
logger.Logf(Note, "", "123", "No filename, only line number.", "No filename, only line number.")
logger.Logf(Note, "filename", "", "Filename without line number.", "Filename without line number.")
logger.Logf(Note, "", "", "Neither filename nor line number.", "Neither filename nor line number.")
t.CheckOutputLines(
"filename:123: note: Both filename and line number.",
"note: No filename, only line number.",
"filename: note: Filename without line number.",
"note: Neither filename nor line number.")
}
func (s *Suite) Test_Logger_Logf__traditional_format(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--gcc-output-format=no")
logger := &G.Logger
logger.Logf(Note, "filename", "123", "Both filename and line number.", "Both filename and line number.")
logger.Logf(Note, "", "123", "No filename, only line number.", "No filename, only line number.")
logger.Logf(Note, "filename", "", "Filename without line number.", "Filename without line number.")
logger.Logf(Note, "", "", "Neither filename nor line number.", "Neither filename nor line number.")
t.CheckOutputLines(
"NOTE: filename:123: Both filename and line number.",
"NOTE: No filename, only line number.",
"NOTE: filename: Filename without line number.",
"NOTE: Neither filename nor line number.")
}
// Ensures that pkglint never destroys the terminal emulator by sending unintended escape sequences.
func (s *Suite) Test_Logger_Logf__strange_characters(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--gcc-output-format", "--source", "--explain")
G.Logger.Logf(Note, "filename", "123", "Format.", "Unicode \U0001F645 and ANSI \x1B are never logged.")
G.Logger.Explain(
"Even a \u0007 in the explanation is silent.")
t.CheckOutputLines(
"filename:123: note: Unicode <U+1F645> and ANSI <U+001B> are never logged.",
"",
"\tEven a <U+0007> in the explanation is silent.",
"")
}
// Even if verbose logging is disabled, the "Replacing" diagnostics
// must not be filtered for duplicates since each of them modifies the line.
func (s *Suite) Test_Logger_Logf__duplicate_autofix(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--explain", "--autofix")
G.Logger.verbose = false // See SetUpTest
line := t.NewLine("README.txt", 123, "text")
fix := line.Autofix()
fix.Warnf("T should always be uppercase.")
fix.Replace("te", "Te")
fix.Replace("t", "T")
fix.Apply()
t.CheckOutputLines(
"AUTOFIX: README.txt:123: Replacing \"te\" with \"Te\".",
"AUTOFIX: README.txt:123: Replacing \"t\" with \"T\".")
}
func (s *Suite) Test_Logger_Logf__panic(c *check.C) {
t := s.Init(c)
t.ExpectPanic(
func() { G.Logger.Logf(Error, "filename", "13", "No period", "No period") },
"Pkglint internal error: Diagnostic format \"No period\" must end in a period.")
}
func (s *Suite) Test_Logger_Logf__wording(c *check.C) {
t := s.Init(c)
t.ExpectPanic(
func() { G.Logger.Logf(Error, "filename", "13", "This should.", "This should.") },
"Pkglint internal error: The word \"should\" must only appear in warnings: This should.")
t.ExpectPanic(
func() { G.Logger.Logf(Warn, "filename", "13", "This must.", "This must.") },
"Pkglint internal error: The word \"must\" must only appear in errors: This must.")
G.Logger.Logf(Note, "filename", "13", "This should.", "This should.")
t.CheckOutputLines(
"NOTE: filename:13: This should.")
}
// In case of a fatal error, pkglint quits in a controlled manner,
// and the trace log shows where the fatal error happened.
func (s *Suite) Test_Logger_TechFatalf__trace(c *check.C) {
t := s.Init(c)
t.EnableTracingToLog()
inner := func() {
defer trace.Call0()()
G.Logger.TechFatalf(
"filename",
"Cannot continue because of %q and %q.", "reason 1", "reason 2")
}
outer := func() {
defer trace.Call0()()
inner()
}
t.ExpectFatal(
outer,
"TRACE: + (*Suite).Test_Logger_TechFatalf__trace.func2()",
"TRACE: 1 + (*Suite).Test_Logger_TechFatalf__trace.func1()",
"TRACE: 1 2 TechFatalf: filename: Cannot continue because of \"reason 1\" and \"reason 2\".",
"TRACE: 1 - (*Suite).Test_Logger_TechFatalf__trace.func1()",
"TRACE: - (*Suite).Test_Logger_TechFatalf__trace.func2()",
"FATAL: filename: Cannot continue because of \"reason 1\" and \"reason 2\".")
}
// Technical errors are not diagnostics.
// Therefore --gcc-output-format has no effect on them.
func (s *Suite) Test_Logger_TechErrorf__gcc_format(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--gcc-output-format")
G.Logger.TechErrorf("filename", "Cannot be opened for %s.", "reading")
t.CheckOutputLines(
"ERROR: filename: Cannot be opened for reading.")
}
func (s *Suite) Test_Logger_ShowSummary__explanations_with_only(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--only", "interesting")
line := t.NewLine("Makefile", 27, "The old song")
// Neither the warning nor the corresponding explanation are logged.
line.Warnf("Filtered warning.")
line.Explain("Explanation for the above warning.")
G.Logger.ShowSummary(t.argv)
// Since the above warning is filtered out by the --only option,
// adding --explain to the options would not show any explanation.
// Therefore, "Run \"pkglint -e\"" is not advertised in this case,
// but see below.
t.CheckEquals(G.Logger.explanationsAvailable, false)
t.CheckOutputLines(
"Looks fine.")
line.Warnf("This warning is interesting.")
line.Explain("This explanation is available.")
G.Logger.ShowSummary(t.argv)
t.CheckEquals(G.Logger.explanationsAvailable, true)
t.CheckOutputLines(
"WARN: Makefile:27: This warning is interesting.",
"1 warning found.",
"(Run \"pkglint -e --only interesting\" to show explanations.)")
}
func (s *Suite) Test_Logger_ShowSummary__looks_fine(c *check.C) {
t := s.Init(c)
logger := Logger{out: NewSeparatorWriter(&t.stdout)}
logger.ShowSummary([]string{"pkglint"})
t.CheckOutputLines(
"Looks fine.")
}
func (s *Suite) Test_Logger_ShowSummary__1_error_1_warning(c *check.C) {
t := s.Init(c)
logger := Logger{out: NewSeparatorWriter(&t.stdout)}
logger.Logf(Error, "", "", ".", ".")
logger.Logf(Warn, "", "", ".", ".")
logger.ShowSummary([]string{"pkglint"})
t.CheckOutputLines(
"ERROR: .",
"WARN: .",
"1 error and 1 warning found.")
}
func (s *Suite) Test_Logger_ShowSummary__2_errors_3_warnings(c *check.C) {
t := s.Init(c)
logger := Logger{out: NewSeparatorWriter(&t.stdout)}
logger.Logf(Error, "", "", "1.", "1.")
logger.Logf(Error, "", "", "2.", "2.")
logger.Logf(Warn, "", "", "3.", "3.")
logger.Logf(Warn, "", "", "4.", "4.")
logger.Logf(Warn, "", "", "5.", "5.")
logger.ShowSummary([]string{"pkglint"})
t.CheckOutputLines(
"ERROR: 1.",
"ERROR: 2.",
"WARN: 3.",
"WARN: 4.",
"WARN: 5.",
"2 errors and 3 warnings found.")
}
func (s *Suite) Test_Logger_ShowSummary__looks_fine_quiet(c *check.C) {
t := s.Init(c)
logger := Logger{out: NewSeparatorWriter(&t.stdout)}
logger.Opts.Quiet = true
logger.ShowSummary([]string{"pkglint"})
t.CheckOutputEmpty()
}
func (s *Suite) Test_Logger_ShowSummary__1_error_1_warning_quiet(c *check.C) {
t := s.Init(c)
logger := Logger{out: NewSeparatorWriter(&t.stdout)}
logger.Opts.Quiet = true
logger.Logf(Error, "", "", ".", ".")
logger.Logf(Warn, "", "", ".", ".")
logger.ShowSummary([]string{"pkglint"})
t.CheckOutputLines(
"ERROR: .",
"WARN: .")
}
func (s *Suite) Test_Logger_ShowSummary__explanations_available(c *check.C) {
t := s.Init(c)
logger := Logger{out: NewSeparatorWriter(&t.stdout)}
logger.Logf(Error, "", "", ".", ".")
logger.Explain(
"Explanation.")
logger.ShowSummary([]string{"pkglint"})
t.CheckOutputLines(
"ERROR: .",
"1 error found.",
"(Run \"pkglint -e\" to show explanations.)")
}
func (s *Suite) Test_Logger_ShowSummary__explanations_available_in_explain_mode(c *check.C) {
t := s.Init(c)
logger := Logger{out: NewSeparatorWriter(&t.stdout)}
logger.Logf(Error, "", "", ".", ".")
logger.Explain(
"Explanation.")
// Since the --explain option is already given, it need not be advertised.
logger.Opts.Explain = true
logger.ShowSummary([]string{"pkglint"})
t.CheckOutputLines(
"ERROR: .",
"1 error found.")
}
func (s *Suite) Test_Logger_ShowSummary__autofix_available(c *check.C) {
t := s.Init(c)
logger := Logger{out: NewSeparatorWriter(&t.stdout)}
logger.autofixAvailable = true // See SaveAutofixChanges
logger.ShowSummary([]string{"pkglint"})
t.CheckOutputLines(
"Looks fine.",
"(Run \"pkglint -fs\" to show what can be fixed automatically.)",
"(Run \"pkglint -F\" to automatically fix some issues.)")
}
func (s *Suite) Test_Logger_ShowSummary__autofix_available_with_show_autofix_option(c *check.C) {
t := s.Init(c)
logger := Logger{out: NewSeparatorWriter(&t.stdout)}
logger.autofixAvailable = true // See SaveAutofixChanges
logger.Opts.ShowAutofix = true
logger.ShowSummary([]string{"pkglint"})
// Since the --show-autofix option is already given, it need not be advertised.
// But the --autofix option is not given, therefore mention it.
t.CheckOutputLines(
"Looks fine.",
"(Run \"pkglint -F\" to automatically fix some issues.)")
}
func (s *Suite) Test_Logger_ShowSummary__autofix_available_with_autofix_option(c *check.C) {
t := s.Init(c)
logger := Logger{out: NewSeparatorWriter(&t.stdout)}
logger.autofixAvailable = true // See SaveAutofixChanges
logger.Opts.Autofix = true
logger.ShowSummary([]string{"pkglint"})
// Since the --autofix option is already given, it need not be advertised.
// Mentioning the --show-autofix option would be pointless here since the
// usual path goes from default mode via --show-autofix to --autofix.
// The usual "x warnings" would also be misleading since the warnings have just
// been fixed by the autofix feature. Therefore the output is completely empty.
t.CheckOutputEmpty()
}
func (s *Suite) Test_Logger_ShowSummary__quoting(c *check.C) {
t := s.Init(c)
logger := Logger{out: NewSeparatorWriter(&t.stdout)}
logger.errors = 1
logger.explanationsAvailable = true
logger.ShowSummary([]string{"pkglint", "--only", "string with 'quotes'"})
t.CheckOutputLines(
"1 error found.",
"(Run \"pkglint -e --only 'string with '\\''quotes'\\'''\" to show explanations.)")
}
func (s *Suite) Test_SeparatorWriter(c *check.C) {
t := s.Init(c)
var sb strings.Builder
wr := NewSeparatorWriter(&sb)
wr.WriteLine("a")
wr.WriteLine("b")
t.CheckEquals(sb.String(), "a\nb\n")
wr.Separate()
t.CheckEquals(sb.String(), "a\nb\n")
wr.WriteLine("c")
t.CheckEquals(sb.String(), "a\nb\n\nc\n")
}
func (s *Suite) Test_NewSeparatorWriter(c *check.C) {
t := s.Init(c)
var sb strings.Builder
wr := NewSeparatorWriter(&sb)
t.CheckEquals(wr.out, &sb)
t.CheckEquals(wr.state, uint8(3))
t.CheckEquals(wr.line.Len(), 0)
}
func (s *Suite) Test_SeparatorWriter_WriteLine(c *check.C) {
t := s.Init(c)
var sb strings.Builder
wr := NewSeparatorWriter(&sb)
wr.WriteLine("first")
wr.Separate()
t.CheckEquals(sb.String(), "first\n")
wr.WriteLine("second")
t.CheckEquals(sb.String(), "first\n\nsecond\n")
}
func (s *Suite) Test_SeparatorWriter_Write(c *check.C) {
t := s.Init(c)
var sb strings.Builder
wr := NewSeparatorWriter(&sb)
wr.Write("first")
wr.Write("")
t.CheckEquals(wr.line.String(), "first")
wr.Write("\n")
t.CheckEquals(wr.line.String(), "")
t.CheckEquals(sb.String(), "first\n")
wr.Separate()
t.CheckEquals(sb.String(), "first\n")
wr.WriteLine("second")
t.CheckEquals(sb.String(), "first\n\nsecond\n")
}
func (s *Suite) Test_SeparatorWriter_Separate(c *check.C) {
t := s.Init(c)
var sb strings.Builder
wr := NewSeparatorWriter(&sb)
wr.WriteLine("a")
wr.Separate()
t.CheckEquals(sb.String(), "a\n")
// The call to Separate had requested an empty line. That empty line
// can either be given explicitly (like here), or it will be written
// implicitly before the next non-newline character.
wr.WriteLine("")
wr.Separate()
t.CheckEquals(sb.String(), "a\n\n")
wr.WriteLine("c")
wr.Separate()
t.CheckEquals(sb.String(), "a\n\nc\n")
}
func (s *Suite) Test_SeparatorWriter_Separate__at_the_beginning(c *check.C) {
t := s.Init(c)
var sb strings.Builder
wr := NewSeparatorWriter(&sb)
wr.Separate()
wr.WriteLine("a")
t.CheckEquals(sb.String(), "a\n")
}
func (s *Suite) Test_SeparatorWriter_write(c *check.C) {
t := s.Init(c)
var sb strings.Builder
wr := NewSeparatorWriter(&sb)
t.CheckEquals(wr.state, uint8(3))
wr.write('a')
t.CheckEquals(wr.state, uint8(1))
wr.write('\n')
t.CheckEquals(wr.state, uint8(0))
wr.Separate()
t.CheckEquals(wr.state, uint8(2))
wr.write('\n')
t.CheckEquals(wr.state, uint8(3))
}
func (s *Suite) Test_SeparatorWriter_Flush(c *check.C) {
t := s.Init(c)
var sb strings.Builder
wr := NewSeparatorWriter(&sb)
wr.Write("a")
wr.Write("b")
t.CheckEquals(sb.String(), "")
wr.Flush()
t.CheckEquals(sb.String(), "ab")
t.ExpectAssert(wr.Separate) // Must not be called in the middle of a line.
wr.WriteLine("")
wr.Separate()
// The current line is terminated immediately by the above Separate(),
// but the empty line for separating two paragraphs is kept in mind.
// It will be added later, before the next non-newline character.
t.CheckEquals(sb.String(), "ab\n")
wr.Write("c")
wr.Flush()
t.CheckEquals(sb.String(), "ab\n\nc")
}