package pkglint

import "gopkg.in/check.v1"

// This is how each patch should look like.
func (s *Suite) Test_CheckLinesPatch__with_comment(c *check.C) {
	t := s.Init(c)

	lines := t.NewLines("patch-WithComment",
		CvsID,
		"",
		"This part describes:",
		"* the purpose of the patch,",
		"* to which operating systems it applies",
		"* either why it is specific to pkgsrc",
		"* or where it has been reported upstream",
		"",
		"--- WithComment.orig",
		"+++ WithComment",
		"@@ -5,3 +5,3 @@",
		" context before",
		"-old line",
		"+new line",
		" context after")

	CheckLinesPatch(lines, nil)

	t.CheckOutputEmpty()
}

// To make the patch comment clearly visible, it should be surrounded by empty lines.
// The missing empty lines are inserted by pkglint.
func (s *Suite) Test_CheckLinesPatch__without_empty_line__autofix(c *check.C) {
	t := s.Init(c)

	t.Chdir("category/package")
	patchLines := t.SetUpFileLines("patch-WithoutEmptyLines",
		CvsID,
		"Text",
		"--- file.orig",
		"+++ file",
		"@@ -5,3 +5,3 @@",
		" context before",
		"-old line",
		"+new line",
		" context after")
	t.CreateFileLines("distinfo",
		CvsID,
		"",
		// The hash is taken from a breakpoint at the beginning of AutofixDistinfo, oldSha1
		"SHA1 (some patch) = 49abd735b7e706ea9ed6671628bb54e91f7f5ffb")

	t.SetUpCommandLine("-Wall", "--autofix")
	pkg := NewPackage(".")

	CheckLinesPatch(patchLines, pkg)

	t.CheckOutputLines(
		"AUTOFIX: patch-WithoutEmptyLines:2: Inserting a line \"\" above this line.",
		"AUTOFIX: patch-WithoutEmptyLines:3: Inserting a line \"\" above this line.",
		"AUTOFIX: distinfo:3: Replacing \"49abd735b7e706ea9ed6671628bb54e91f7f5ffb\" "+
			"with \"4938fc8c0b483dc2e33e741b0da883d199e78164\".")

	t.CheckFileLines("patch-WithoutEmptyLines",
		CvsID,
		"",
		"Text",
		"",
		"--- file.orig",
		"+++ file",
		"@@ -5,3 +5,3 @@",
		" context before",
		"-old line",
		"+new line",
		" context after")
	t.CheckFileLines("distinfo",
		CvsID,
		"",
		"SHA1 (some patch) = 4938fc8c0b483dc2e33e741b0da883d199e78164")
}

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

	patchLines := t.SetUpFileLines("patch-WithoutEmptyLines",
		CvsID,
		"--- WithoutEmptyLines.orig",
		"+++ WithoutEmptyLines",
		"@@ -1,1 +1,1 @@",
		"-old line",
		"+new line")

	CheckLinesPatch(patchLines, nil)

	// These duplicate notes are actually correct. There should be an
	// empty line above the documentation and one below it. Since there
	// is no documentation yet, the line number for above and below is
	// the same. Outside of the testing environment, this duplicate
	// diagnostic is suppressed; see LogVerbose.
	t.CheckOutputLines(
		"NOTE: ~/patch-WithoutEmptyLines:2: Empty line expected above this line.",
		"ERROR: ~/patch-WithoutEmptyLines:2: Each patch must be documented.",
		"NOTE: ~/patch-WithoutEmptyLines:2: Empty line expected.")
}

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

	lines := t.NewLines("patch-WithoutComment",
		CvsID,
		"",
		"--- WithoutComment.orig",
		"+++ WithoutComment",
		"@@ -5,3 +5,3 @@",
		" context before",
		"-old line",
		"+old line",
		" context after")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"ERROR: patch-WithoutComment:3: Each patch must be documented.")
}

// The output of BSD Make typically contains "*** Error code".
// In some really good patches, this output is included in the patch comment,
// to document why the patch is necessary.
func (s *Suite) Test_CheckLinesPatch__error_code(c *check.C) {
	t := s.Init(c)

	lines := t.NewLines("patch-ErrorCode",
		CvsID,
		"",
		"*** Error code 1", // Looks like a context diff but isn't.
		"",
		"--- ErrorCode.orig",
		"+++ ErrorCode",
		"@@ -5,3 +5,3 @@",
		" context before",
		"-old line",
		"+old line",
		" context after")

	CheckLinesPatch(lines, nil)

	t.CheckOutputEmpty()
}

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

	lines := t.NewLines("patch-WrongOrder",
		CvsID,
		"",
		"Text",
		"Text",
		"",
		"+++ WrongOrder",      // Wrong order
		"--- WrongOrder.orig", // Wrong order
		"@@ -5,3 +5,3 @@",
		" context before",
		"-old line",
		"+old line",
		" context after")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"WARN: patch-WrongOrder:7: Unified diff headers should be first ---, then +++.")
}

// Context diffs are old and deprecated. Therefore pkglint doesn't check them thoroughly.
func (s *Suite) Test_CheckLinesPatch__context_diff(c *check.C) {
	t := s.Init(c)

	lines := t.NewLines("patch-ctx",
		CvsID,
		"",
		"diff -cr history.c.orig history.c",
		"*** history.c.orig",
		"--- history.c")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"ERROR: patch-ctx:4: Each patch must be documented.",
		"WARN: patch-ctx:4: Please use unified diffs (diff -u) for patches.")
}

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

	lines := t.NewLines("patch-aa",
		CvsID,
		"",
		"-- oldfile",
		"++ newfile")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"ERROR: patch-aa: Contains no patch.")
}

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

	lines := t.NewLines("patch-aa",
		CvsID,
		"",
		"A single patch file can apply to more than one file at a time.",
		"It shouldn't though, to keep the relation between patch files",
		"and patched files simple.",
		"",
		"--- oldfile",
		"+++ newfile",
		"@@ -1 +1 @@",
		"-old",
		"+new",
		"--- oldfile2",
		"+++ newfile2",
		"@@ -1 +1 @@",
		"-old",
		"+new")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"WARN: patch-aa: Contains patches for 2 files, should be only one.")
}

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

	lines := t.NewLines("patch-CVE-2019-0001",
		CvsID,
		"",
		"Patches that are provided by upstream for a specific topic don't",
		"need to be split artificially.",
		"",
		"--- oldfile",
		"+++ newfile",
		"@@ -1 +1 @@",
		"-old",
		"+new",
		"--- oldfile2",
		"+++ newfile2",
		"@@ -1 +1 @@",
		"-old",
		"+new")

	CheckLinesPatch(lines, nil)

	t.CheckOutputEmpty()
}

// The patch headers are only recognized as such if they appear directly below each other.
func (s *Suite) Test_CheckLinesPatch__documentation_that_looks_like_patch_lines(c *check.C) {
	t := s.Init(c)

	lines := t.NewLines("patch-aa",
		CvsID,
		"",
		"--- oldfile",
		"",
		"+++ newfile",
		"",
		"*** oldOrNewFile")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"ERROR: patch-aa: Contains no patch.")
}

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

	lines := t.NewLines("patch-unified",
		CvsID,
		"",
		"Documentation for the patch",
		"",
		"--- unified.orig",
		"+++ unified")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"ERROR: patch-unified:EOF: No patch hunks for \"unified\".")
}

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

	lines := t.NewLines("patch-context",
		CvsID,
		"",
		"Documentation for the patch",
		"",
		"*** file.orig",
		"--- file")

	CheckLinesPatch(lines, nil)

	// Context diffs are deprecated, therefore it is not worth
	// adding extra code for checking them thoroughly.
	t.CheckOutputLines(
		"WARN: patch-context:5: Please use unified diffs (diff -u) for patches.")
}

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

	lines := t.NewLines("patch-aa",
		CvsID,
		"",
		"comment",
		"",
		"--- oldfile",
		"+++ newfile",
		"@@ -1 +1 @@",
		"-old",
		"\\ No newline at end of file",
		"+new",
		"\\ No newline at end of file",
		"last line (a comment)")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"WARN: patch-aa:12: Empty line or end of file expected.")
}

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

	lines := t.NewLines("patch-aa",
		CvsID,
		"",
		"comment",
		"",
		"--- oldfile",
		"+++ newfile",
		"@@ -1 +1 @@",
		"-old",
		"\\ No newline at end of file",
		"+new",
		"\\ No newline at end of file")

	CheckLinesPatch(lines, nil)

	t.CheckOutputEmpty()
}

// Some patch files may end before reaching the expected line count (in this case 7 lines).
// This is ok if only context lines are missing. These context lines are assumed to be empty lines.
func (s *Suite) Test_CheckLinesPatch__empty_lines_left_out_at_eof(c *check.C) {
	t := s.Init(c)

	lines := t.NewLines("patch-aa",
		CvsID,
		"",
		"comment",
		"",
		"--- oldfile",
		"+++ newfile",
		"@@ -1,7 +1,6 @@",
		" 1",
		" 2",
		" 3",
		"-4",
		" 5",
		" 6") // Line 7 was empty, therefore omitted

	CheckLinesPatch(lines, nil)

	t.CheckOutputEmpty()
}

// In some context lines, the leading space character may be missing.
// Since this is no problem for patch(1), pkglint also doesn't complain.
func (s *Suite) Test_CheckLinesPatch__context_lines_with_tab_instead_of_space(c *check.C) {
	t := s.Init(c)

	lines := t.NewLines("patch-aa",
		CvsID,
		"",
		"comment",
		"",
		"--- oldfile",
		"+++ newfile",
		"@@ -1,3 +1,3 @@",
		"\tcontext",
		"-old",
		"+new",
		"\tcontext")

	CheckLinesPatch(lines, nil)

	t.CheckOutputEmpty()
}

// Before 2018-01-28, pkglint had panicked when checking an empty
// patch file, as a slice index was out of bounds.
func (s *Suite) Test_CheckLinesPatch__autofix_empty_patch(c *check.C) {
	t := s.Init(c)

	t.SetUpCommandLine("-Wall", "--autofix")
	lines := t.NewLines("patch-aa",
		CvsID)

	CheckLinesPatch(lines, nil)

	t.CheckOutputEmpty()
}

// Before 2018-01-28, pkglint had panicked when checking an empty
// patch file, as a slice index was out of bounds.
func (s *Suite) Test_CheckLinesPatch__autofix_long_empty_patch(c *check.C) {
	t := s.Init(c)

	t.SetUpCommandLine("-Wall", "--autofix")
	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"")

	CheckLinesPatch(lines, nil)

	t.CheckOutputEmpty()
}

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

	t.SetUpCommandLine("-Wall", "--autofix")
	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"Documentation",
		"",
		"--- oldfile",
		"+++ newfile",
		"@@ -1,1 +1,1 @@\r",
		"-old line",
		"+new line")

	CheckLinesPatch(lines, nil)

	// To relieve the pkgsrc package maintainers from this boring work,
	// the pkgsrc infrastructure could fix these issues before actually
	// applying the patches.
	t.CheckOutputLines(
		"AUTOFIX: ~/patch-aa:7: Replacing \"\\r\\n\" with \"\\n\".")
}

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

	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"Documentation",
		"",
		"--- configure.orig",
		"+++ configure",
		"@@ -1,1 +1,1 @@",
		"-old line",
		"+: Avoid regenerating within pkgsrc")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"ERROR: ~/patch-aa:9: This code must not be included in patches.")
}

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

	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"Documentation",
		"",
		"--- configure.orig",
		"+++ configure",
		"@@ -1,3 +1,3 @@",
		"",
		"-old line",
		"+new line")

	CheckLinesPatch(lines, nil)

	// The first context line should start with a single space character,
	// but that would mean trailing whitespace, so it may be left out.
	// The last context line is omitted completely because it would also
	// have trailing whitespace, and if that were removed, would be a
	// trailing empty line.
	t.CheckOutputEmpty()
}

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

	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"Documentation",
		"",
		"--- configure.orig",
		"+++ configure",
		"@@ -1,3 +1,3 @@",
		"",
		"-old line",
		"<<<<<<<<",
		"+new line")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"ERROR: ~/patch-aa:10: Invalid line in unified patch hunk: <<<<<<<<")
}

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

	lines := t.SetUpFileLines("patch-aa",
		"This first line is missing the CVS Id",
		"",
		"Documentation")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		sprintf("ERROR: ~/patch-aa:1: Expected %q.", CvsID),
		"NOTE: ~/patch-aa:1: Empty line expected above this line.",
		"ERROR: ~/patch-aa: Contains no patch.")
}

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

	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"This patch creates a new file.",
		"",
		"--- /dev/null",
		"+++ added-file",
		"@@ -0,0 +1,1 @@",
		"+ added line")

	CheckLinesPatch(lines, nil)

	t.CheckOutputEmpty()
}

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

	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"This patch deletes an existing file.",
		"",
		"--- deleted-file",
		"+++ /dev/null",
		"@@ -1,1 +0,0 @@",
		"- deleted line")

	CheckLinesPatch(lines, nil)

	t.CheckOutputEmpty()
}

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

	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"This patch deletes an existing file.",
		"",
		"--- /absolute",
		"+++ /absolute",
		"@@ -1,1 +1,1 @@",
		"- deleted line",
		"+ added line")

	CheckLinesPatch(lines, nil)

	// XXX: Patches must not apply to absolute paths.
	// The only allowed exception is /dev/null.
	// ^(---|\+\+\+) /(?!dev/null)
	t.CheckOutputEmpty()
}

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

	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"This patch wrongly contains the hard-coded PREFIX.",
		"",
		"--- Makefile",
		"+++ Makefile",
		"@@ -1,1 +1,1 @@",
		"- prefix := @prefix@",
		"+ prefix := /usr/pkg")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"ERROR: ~/patch-aa:9: Patches must not hard-code the pkgsrc PREFIX.")
}

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

	lines := t.NewLines("patch-aa",
		CvsID,
		"",
		"Documentation",
		"",
		"--- old",
		"+++ new",
		"@@ -1,1 +1,1 @@",
		"- old",
		"+ new",
		"",
		"This line is not part of the patch. Since it is separated from",
		"the patch by an empty line, there is no reason for a warning.")

	CheckLinesPatch(lines, nil)

	t.CheckOutputEmpty()
}

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

	lines := t.NewLines("patch-aa",
		CvsID,
		"",
		"Documentation",
		"",
		"--- old",
		"+++ new",
		"@@ -2,1 +1,1 @@",
		"- old",
		"+ new",
		"@@ -5,1 +7,1 @@",
		"- old",
		"+ new")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"NOTE: patch-aa:7: The difference between the line numbers 2 and 1 should be 0, not -1.",
		"NOTE: patch-aa:10: The difference between the line numbers 5 and 7 should be 0, not 2.")
}

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

	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"--- old",
		"+++ new",
		"@@ -1,1 +1,1 @@",
		"- old",
		"+ new",
		"",
		"--- old",
		"+++ new",
		"@@ -1,1 +1,1 @@",
		"- old",
		"+ new")

	CheckLinesPatch(lines, nil)

	// The "must be documented" error message is only given before the first
	// patch since that's the only place where the documentation is expected.
	// Since each pkgsrc patch should only patch a single file, this situation
	// is an edge case anyway.
	t.CheckOutputLines(
		"ERROR: ~/patch-aa:3: Each patch must be documented.",
		"WARN: ~/patch-aa: Contains patches for 2 files, should be only one.")
}

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

	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"Documentation",
		"",
		"--- configure.sh.orig",
		"+++ configure.sh",
		"@@ -1,1 +1,1 @@",
		"-old line",
		"+: Avoid regenerating within pkgsrc")

	CheckLinesPatch(lines, nil)

	// No warning since configure.sh is probably not a GNU-style
	// configure file.
	t.CheckOutputEmpty()
}

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

	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"Documentation",
		"",
		"--- configure.orig",
		"+++ configure",
		"@@ -1,1 +1,1 @@",
		"-old line",
		"+: Avoid regenerating within pkgsrc")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"ERROR: ~/patch-aa:9: This code must not be included in patches.")
}

// I'm not sure whether configure.in is really relevant for this check.
// As of December 2019, there is absolutely no package that uses
// CONFIGURE_SCRIPTS_OVERRIDE.
func (s *Suite) Test_PatchChecker_checkConfigure__configure_in(c *check.C) {
	t := s.Init(c)

	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"Documentation",
		"",
		"--- configure.in.orig",
		"+++ configure.in",
		"@@ -1,1 +1,1 @@",
		"-old line",
		"+: Avoid regenerating within pkgsrc")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"ERROR: ~/patch-aa:9: This code must not be included in patches.")
}

// I'm not sure whether configure.ac is really relevant for this check.
// As of December 2019, there is absolutely no package that uses
// CONFIGURE_SCRIPTS_OVERRIDE.
func (s *Suite) Test_PatchChecker_checkConfigure__configure_ac(c *check.C) {
	t := s.Init(c)

	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"Documentation",
		"",
		"--- configure.ac.orig",
		"+++ configure.ac",
		"@@ -1,1 +1,1 @@",
		"-old line",
		"+: Avoid regenerating within pkgsrc")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"ERROR: ~/patch-aa:9: This code must not be included in patches.")
}

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

	lines := t.NewLines("patch-aa",
		CvsID,
		"",
		"Documentation.",
		"",
		"--- a/aa",
		"+++ b/aa",
		"@@ -1,1 +1,1 @@",
		"-old",
		"+#! /home/my/pkgsrc/pkg/bin/bash")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"ERROR: patch-aa:9: Patches must not add a hard-coded interpreter " +
			"(/home/my/pkgsrc/pkg/bin/bash).")
}

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

	lines := t.NewLines("patch-aa",
		CvsID,
		"",
		"Documentation.",
		"",
		"--- a/aa",
		"+++ b/aa",
		"@@ -1,1 +1,2 @@",
		"-old",
		"+new",
		"+#! /home/my/pkgsrc/pkg/bin/bash")

	CheckLinesPatch(lines, nil)

	t.CheckOutputEmpty()
}

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

	lines := t.NewLines("patch-aa",
		CvsID,
		"",
		"Documentation.",
		"",
		"--- a/aa",
		"+++ b/aa",
		"@@ -1,1 +1,1 @@",
		"-old",
		"+#! @PREFIX@/bin/bash")

	CheckLinesPatch(lines, nil)

	t.CheckOutputEmpty()
}

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

	test := func(addedLine string, diagnostics ...string) {
		lines := t.NewLines("patch-file",
			CvsID,
			"",
			"Demonstrates absolute paths.",
			"",
			"--- file.orig",
			"+++ file",
			"@@ -1,0 +1,1 @@",
			"+"+addedLine)

		CheckLinesPatch(lines, nil)

		t.CheckOutput(diagnostics)
	}

	test(
		"/usr/pkg",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc PREFIX.")

	test(
		"/usr/pkgsrc",
		nil...)

	test(
		"/usr/pkg/bin",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc PREFIX.")

	test(
		"/usr/local:/usr/pkg:/opt",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc PREFIX.")

	test(
		"/var",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc VARBASE.")

	test(
		"/var/db",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc VARBASE.")

	test(
		"/var/run",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc VARBASE.")

	// A well-known path that is not specific to pkgsrc.
	test(
		"/var/shm",
		nil...)

	// A well-known path that is not specific to pkgsrc.
	test(
		"/var/tmp",
		nil...)

	test(
		"/etc",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc PKG_SYSCONFDIR.")

	// BSD-style Makefile
	test(
		"${PREFIX}/etc",
		nil...)

	// GNU automake-style Makefile
	test(
		"$(prefix)/etc",
		nil...)

	// C source code.
	// Instead of PREFIX/etc, this should rather be PKG_SYSCONFDIR.
	// This is a relative path because of the PREFIX.
	test(
		"const char *conf_dir = PREFIX \"/etc\"",
		nil...)

	// CMakeLists.txt
	test(
		"set(ETC_DIR \"/etc\")",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc PKG_SYSCONFDIR.")

	test(
		"/etc/mk.conf",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc PKG_SYSCONFDIR.")

	test(
		"/etc/rc.d/daemon",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc PKG_SYSCONFDIR.")

	test(
		"/usr/pkg and /var and /etc",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc PREFIX.",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc VARBASE.",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc PKG_SYSCONFDIR.")

	// From the --help text of a GNU configure script.
	test(
		"[PREFIX/etc]",
		nil...)

	// Shell program, default value for a variable.
	test(
		"DIR=${DIR-/var/bytebench}",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc VARBASE.")

	// Shell program or Makefile.
	// The placeholder will make this a relative path.
	test(
		"dir=@prefix@/etc",
		nil...)

	// Makefile with flags for the C compiler.
	test(
		"CFLAGS+= -I/usr/pkg/include",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc PREFIX.")

	// Makefile with flags for the linker.
	test(
		"LDFLAGS+= -L/usr/pkg/lib",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc PREFIX.")

	// Makefile with flags for the linker.
	// There should be an additional warning for using COMPILER_RPATH_FLAG.
	test(
		"LDFLAGS+= -rpath/usr/pkg/lib",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc PREFIX.")

	// Makefile with flags for the linker.
	// There should be an additional warning for using COMPILER_RPATH_FLAG.
	test(
		"LDFLAGS+= -Wl,-R/usr/pkg/lib",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc PREFIX.")

	// The dot before the "/etc" makes it a relative pathname.
	test(
		"cp ./etc/hostname /tmp",
		nil...)

	// +>	+#	from /etc/inittab (SYSV systems)
	// +ERROR: devel/tet3/patches/patch-ac:51: Patches must not hard-code the pkgsrc PKG_SYSCONFDIR.

	test(
		"# SysV /etc/install, /usr/sbin/install",
		nil...)

	// C or C++ program, macro definition.
	// This is an absolute path since the PID_FILE is the macro name,
	// and not part of the macro body containing the path.
	test(
		"#define PID_FILE \"/var/run/daemon.pid\"",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc VARBASE.")

	// This is a relative path because of the PREFIX before it.
	test(
		"#define PID_FILE PREFIX \"/etc/conf\"",
		nil...)

	// In C-style block comments, absolute pathnames are ok,
	// even though they could still be confusing.
	test(
		"#define L 150 /* Length of a line in /etc/some/file */",
		nil...)

	// In multiline C-style block comment, absolute pathnames are ok,
	// even though they could still be confusing.
	test(
		" * Length of a line in /etc/some/file",
		nil...)

	// In C-style end-of-line comments, absolute pathnames are ok,
	//	// even though they could still be confusing.
	test(
		"#define L 150 // Length of a line in /etc/some/file",
		nil...)

	// Allow /etc/passwd, /etc/shadow, /etc/hosts and their variants.
	// These belong to the base system, not to pkgsrc.
	test(
		"#define PATH_SHADOW \"/etc/master.passwd\"",
		nil...)

	test(
		"#define PID_FILE \"/var/run/daemon.pid\" /* comment */",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc VARBASE.")

	// This is a rather theoretical case.
	// Don't worry if pkglint doesn't complain about the absolute path here.
	test(
		"#define PID_FILE /* */ \"/var/run/daemon.pid\" /* */",
		nil...)

	// The absolute path occurs in a comment that is only opened but not closed.
	// It's an edge case, and it may or may not be justified to complain here.
	test(
		"/* See /var/run/daemon.pid for details.",
		"ERROR: patch-file:8: Patches must not hard-code the pkgsrc VARBASE.")

	// Absolute paths in shell comments are not that dangerous.
	test(
		"# See /var/run/daemon.pid for details.",
		nil...)
}

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

	lines := t.SetUpFileLines("patch-aa",
		CvsID,
		"",
		"Documentation",
		"",
		"--- configure.sh.orig",
		"+++ configure.sh",
		"@@ -1,3 +1,3 @@ $"+"Id$",
		" $"+"Id$",
		"-old line",
		"+new line $varname",
		" $"+"Author: authorship $")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"WARN: ~/patch-aa:7: Found CVS tag \"$"+"Id$\". Please remove it.",
		"WARN: ~/patch-aa:8: Found CVS tag \"$"+"Id$\". Please remove it by reducing the number of context lines using pkgdiff or \"diff -U[210]\".",
		"WARN: ~/patch-aa:11: Found CVS tag \"$"+"Author$\". Please remove it by reducing the number of context lines using pkgdiff or \"diff -U[210]\".")
}

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

	test := func(patchName CurrPath, patchedFile Path, diagnostics ...string) {
		ck := PatchChecker{lines: t.NewLines(patchName)}

		ck.checkCanonicalPatchName(patchedFile)

		t.CheckOutput(diagnostics)
	}

	test(
		"patch-aa",
		"any-file.c",
		nil...)

	test(
		"patch-src_main.c",
		"src/main.c",
		nil...)

	// By converting the ".c" to "_c", file managers that only inspect
	// the file extension don't get confused.
	test(
		"patch-src_main_c",
		"src/main.c",
		nil...)

	// CVE patches may patch anything.
	// They may even patch more than one file.
	// Having the source clearly named in the patch file is more important
	// than having a patch name that corresponds to the patched file.
	test(
		"patch-CVE-2020-0001",
		"src/anything.c",
		nil...)

	// Same for Xen Security Advisories.
	test(
		"patch-XSA-0001",
		"src/anything.c",
		nil...)

	test(
		"patch-file_underscore.py",
		"file_underscore.py",
		nil...)

	test(
		"patch-one.py",
		"two.py",
		"WARN: patch-one.py: The patch file should be named \"patch-two.py\" "+
			"to match the patched file \"two.py\".")

	// Don't suggest patch-._file as the patch name since that is unusual.
	test(
		"patch-file",
		"./file",
		nil...)

	// This is usually ok, assuming that the same file does not occur
	// in other directories as well.
	test(
		"patch-file",
		"./src/subdir/file",
		nil...)

	// It's not enough if the patch name is an arbitrary suffix of the
	// patched file. The only allowed abbreviation is the basename.
	test(
		"patch-c",
		"./src/subdir/file.c",
		"WARN: patch-c: The patch file should be named \"patch-src_subdir_file.c\" "+
			"to match the patched file \"./src/subdir/file.c\".")

	// Allow existing patches to differ in case.
	// Most packages won't have files that could conflict on a
	// case-insensitive filesystem anyway.
	test(
		"patch-upper",
		"./src/UPPER",
		nil...)
}

// Autogenerated "comments" from Git or other tools don't count as real
// comments since they don't convey any intention of a human developer.
func (s *Suite) Test_PatchChecker_isEmptyLine(c *check.C) {
	t := s.Init(c)

	lines := t.NewLines("patch-aa",
		CvsID,
		"",
		"diff --git a/aa b/aa",
		"index 1234567..1234567 100644",
		"Index: from Subversion",
		"============= separator or conflict marker",
		"",
		"--- a/aa",
		"+++ b/aa",
		"@@ -1,1 +1,1 @@",
		"-old",
		"+new")

	CheckLinesPatch(lines, nil)

	t.CheckOutputLines(
		"ERROR: patch-aa:8: Each patch must be documented.")
}
