#!/usr/bin/env python3
"""Trap cases and control tests for oss-strip-comments.py.

Each trap is a place where a comment marker is not a comment. Each control
swaps in a deliberately wrong stripper and asserts that the oracle catches it,
so a passing run proves the oracles can fail.
"""
import importlib.util
import pathlib
import sys
import unittest

_HERE = pathlib.Path(__file__).resolve().parent
_spec = importlib.util.spec_from_file_location("strip", _HERE / "oss-strip-comments.py")
s = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(s)
P = pathlib.Path

SH = ('#!/bin/bash\n# whole\nf() { # trail\n'
      '  echo "a # b" \'c # d\' ${x#y} $# ${#arr[@]} # t2\n'
      '  cat <<EOF\n# heredoc data\nEOF\n  case $x in *#*) : ;; esac\n}\n')
AWK = ("#!/bin/bash\nawk -F, '\nBEGIN { # c\n  # c2\n"
       "  x = \"a # b\"; if ($0 ~ /#x/) y = 4 / 2 # c3\n}' f | sort  # t\n"
       "awk '{ # c\n print \"'\"$x\"'\" } # c9' f\n")
YML = 'a: 1 # c\n# whole\nb: "x # y"\nc: |\n  # data\n  z\nd: \'p #q\'\ne: http://h/#frag\n'
PY = ('#!/usr/bin/env python3\n"""mod doc"""\nimport os  # c\n# whole\n'
      'def f():\n    """only doc"""\nclass A:\n    """d"""\n    x = "# not"  # yes\n')
JAVA = ('class A { // c\n  /** doc */\n  String u = "http://x"; /* b */ char q = \'"\';\n'
        '  String t = """\n   // in block\n   """;\n}\n')
DOCKER = '# syntax=docker/dockerfile:1\n# c\nFROM x\nRUN rm -rf /var/lib/apt/lists/* # not a comment\n'
GO = 'package main\n//go:embed x\n// c\nvar s = `// raw`\nfunc main() {} // t\n'
PROPS = '# c\n! c2\n// junk\na=b # c\n'
XML = '<a><!-- c --><b><![CDATA[<!-- data -->]]></b></a>\n'


class Traps(unittest.TestCase):
    def strip(self, name, src):
        s.UNVERIFIED.clear()
        return s.strip_text(P(name), src)

    def test_sh(self):
        out = self.strip("a.sh", SH)
        self.assertNotIn("whole", out); self.assertNotIn("trail", out); self.assertNotIn("t2", out)
        for keep in ("#!/bin/bash", '"a # b"', "'c # d'", "${x#y}", "$#", "${#arr[@]}",
                     "# heredoc data", "*#*"):
            self.assertIn(keep, out)

    def test_awk_including_spliced(self):
        out = self.strip("w.sh", AWK)
        for gone in ("# c", "# c2", "# c3", "# t", "# c9"):
            self.assertNotIn(gone + "\n", out + "\n")
        for keep in ('"a # b"', "/#x/", "4 / 2", "'\"$x\"'"):
            self.assertIn(keep, out)
        self.assertEqual(s.UNVERIFIED, [])

    def test_yaml(self):
        out = self.strip("b.yml", YML)
        self.assertEqual(out.count("#"), 4)              # x # y, # data, p #q, #frag

    def test_python(self):
        out = self.strip("c.py", PY)
        self.assertTrue(out.startswith("#!/usr/bin/env python3\n"))
        self.assertNotIn("doc", out); self.assertNotIn("whole", out)
        self.assertIn("pass", out); self.assertIn('"# not"', out)

    def test_java(self):
        out = self.strip("D.java", JAVA)
        self.assertNotIn("doc", out); self.assertNotIn("/* b */", out)
        self.assertIn('"http://x"', out); self.assertIn("// in block", out); self.assertIn("'\"'", out)

    def test_dockerfile(self):
        out = self.strip("Dockerfile.java", DOCKER)
        self.assertIn("# syntax=", out); self.assertNotIn("# c\n", out)
        self.assertIn("lists/* # not a comment", out)

    def test_go(self):
        out = self.strip("m.go", GO)
        self.assertIn("//go:embed x", out); self.assertIn("`// raw`", out)
        self.assertNotIn("// c", out); self.assertNotIn("// t", out)

    def test_properties(self):
        self.assertEqual(self.strip("p.properties", PROPS), "a=b # c\n")

    def test_xml(self):
        self.assertEqual(self.strip("x.xml", XML), "<a><b><![CDATA[<!-- data -->]]></b></a>\n")

    def test_licence_exempt(self):
        self.assertIsNone(s.stripper_for(P("mvnw")))


class Controls(unittest.TestCase):
    """A wrong stripper must be caught by the oracle for its language."""

    def assertCaught(self, ext, broken, name, src):
        saved = s.STRIPPERS[ext]
        s.STRIPPERS[ext] = (broken, saved[1])
        try:
            with self.assertRaises(SystemExit):
                s.strip_text(P(name), src)
        finally:
            s.STRIPPERS[ext] = saved

    def test_sh(self):
        self.assertCaught(".sh", lambda t: t.replace("echo", "echx"), "a.sh", SH)

    def test_py(self):
        self.assertCaught(".py", lambda t: t.replace("import os", "import sys"), "c.py", PY)

    def test_yaml(self):
        self.assertCaught(".yml", lambda t: t.replace('b: "x # y"', 'b: "x"       '), "b.yml", YML)

    def test_go(self):
        self.assertCaught(".go", lambda t: t.replace("`// raw`", "`      `"), "m.go", GO)

    def test_xml(self):
        self.assertCaught(".xml", lambda t: t.replace("<b>", "<c>").replace("</b>", "</c>"), "x.xml", XML)

    def test_awk(self):
        saved = s.strip_awk
        s.strip_awk = lambda p: saved(p).replace("4 / 2", "4 / 3")
        try:
            with self.assertRaises(SystemExit):
                s.strip_text(P("w.sh"), AWK)
        finally:
            s.strip_awk = saved


if __name__ == "__main__":
    sys.exit(unittest.main())
