changeset 71:88adf10be709 draft

Add tests.
author David Barts <n5jrn@me.com>
date Mon, 15 Jul 2019 13:16:31 -0700
parents a78c74c73d98
children e8b3b336e63e
files run-tests tests/__init__.py tests/suite_01_bad_errors/__init__.py tests/suite_01_bad_errors/files/WEB-INF/.hidden tests/suite_01_bad_errors/files/bad_errors.pspx tests/suite_02_good_errors/__init__.py tests/suite_02_good_errors/files/WEB-INF/.hidden tests/suite_02_good_errors/files/good_errors_1.pspx tests/suite_02_good_errors/files/good_errors_2.pspx tests/suite_02a_good_errors/__init__.py tests/suite_02a_good_errors/files/WEB-INF/.hidden tests/suite_02a_good_errors/files/good_errors.pspx tests/suite_02b_good_errors/__init__.py tests/suite_02b_good_errors/files/WEB-INF/.hidden tests/suite_02b_good_errors/files/good_errors.pspx tests/suite_03_good_forward/__init__.py tests/suite_03_good_forward/files/WEB-INF/.hidden tests/suite_03_good_forward/files/original.pspx tests/suite_03_good_forward/files/relative.pspx tests/suite_03_good_forward/files/same.pspx tests/suite_03_good_forward/files/subdir/absolute.pspx tests/suite_03_good_forward/files/subdir/relative.pspx tests/suite_04_bad_forward_too_many_dotdots/__init__.py tests/suite_04_bad_forward_too_many_dotdots/files/WEB-INF/.hidden tests/suite_04_bad_forward_too_many_dotdots/files/bad.pspx tests/suite_05_bad_forward_not_pspx/__init__.py tests/suite_05_bad_forward_not_pspx/boogers.html tests/suite_05_bad_forward_not_pspx/files/WEB-INF/.hidden tests/suite_05_bad_forward_not_pspx/files/bad.pspx tests/suite_06_bad_forward_missing/__init__.py tests/suite_06_bad_forward_missing/files/WEB-INF/.hidden tests/suite_06_bad_forward_missing/files/bad.pspx tests/suite_06_bad_forward_missing/files/good_errors_1.pspx tests/suite_06_bad_forward_missing/files/good_errors_2.pspx tests/suite_07_bad_forward_garbage/__init__.py tests/suite_07_bad_forward_garbage/files/WEB-INF/.hidden tests/suite_07_bad_forward_garbage/files/bad.pspx tests/suite_07_bad_forward_garbage/files/garbage.dat tests/suite_08_error_error/__init__.py tests/suite_08_error_error/files/404.pspx tests/suite_08_error_error/files/500.pspx tests/suite_08_error_error/files/WEB-INF/.hidden tests/suite_09_bad_forward_norm_error/__init__.py tests/suite_09_bad_forward_norm_error/files/WEB-INF/.hidden tests/suite_09_bad_forward_norm_error/files/error.pspx tests/suite_09_bad_forward_norm_error/files/normal.pspx tests/suite_10_bad_forward_error_norm/__init__.py tests/suite_10_bad_forward_error_norm/files/WEB-INF/.hidden tests/suite_10_bad_forward_error_norm/files/error.pspx tests/suite_10_bad_forward_error_norm/files/normal.pspx tests/suite_11_hidden/__init__.py tests/suite_11_hidden/files/WEB-INF/.hidden tests/suite_11_hidden/files/forward.pspx tests/suite_11_hidden/files/hidden.pspx tests/suite_11_hidden/files/nothidden.pspx tests/suite_12_methods/__init__.py tests/suite_12_methods/files/WEB-INF/.hidden tests/suite_12_methods/files/WEB-INF/lib/jsdict.py tests/suite_12_methods/files/name.pspx tests/suite_12_methods/files/name.py tests/suite_12_methods/files/name_lc.pspx tests/suite_13_bad_python/__init__.py tests/suite_13_bad_python/files/WEB-INF/.hidden tests/suite_13_bad_python/files/basura.py tests/suite_13_bad_python/files/garbage.pspx tests/suite_13_bad_python/files/garbage2.pspx tests/suite_13_bad_python/files/garbage2.py tests/suite_13_bad_python/files/hello.pspx tests/suite_13_bad_python/files/hello.py tests/suite_13_bad_python/files/missing.pspx tests/suite_13_bad_python/files/not_py.pspx tests/suite_13_bad_python/files/too_many_dotdots.pspx tests/suite_14_bad_template/__init__.py tests/suite_14_bad_template/files/WEB-INF/.hidden tests/suite_14_bad_template/files/garbage.pspx tests/suite_14_bad_template/files/garbage2.pspx tests/suite_14_bad_template/files/hello.pspx tests/suite_14_bad_template/files/hello.py tests/suite_14_bad_template/files/missing.pspx tests/suite_14_bad_template/files/not_pspx.dat tests/suite_14_bad_template/files/not_pspx.pspx tests/suite_14_bad_template/files/too_many_dotdots.pspx tests/suite_15_prog_forward/__init__.py tests/suite_15_prog_forward/files/WEB-INF/.hidden tests/suite_15_prog_forward/files/bad.pspx tests/suite_15_prog_forward/files/bad.py tests/suite_15_prog_forward/files/bad2.pspx tests/suite_15_prog_forward/files/bad2.py tests/suite_15_prog_forward/files/good.pspx tests/suite_15_prog_forward/files/good.py tests/suite_15_prog_forward/files/hello.pspx tests/suite_15_prog_forward/files/hello.py
diffstat 91 files changed, 993 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/run-tests	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+# Run our tests. Basically, these use the python unittest framework, but
+# are organized into a directory tree containing test code and the server
+# files to run the tests against. The tests are done by creating a local
+# server on the specified port (default 8080) and issuing requests to it.
+#
+# Note that some of the test suites we run require the requests and/or
+# Beautiful Soup libraries (these are not needed for running TInCan, only
+# for testing it.)
+
+# I m p o r t s
+
+import os, sys
+import importlib
+import logging
+import unittest
+from argparse import ArgumentParser
+
+# V a r i a b l e s
+
+MYNAME = os.path.basename(sys.argv[0])
+TESTDIR = "tests"
+FPREFIX = "suite_"
+FILESDIR = "files"
+FILESVAR = "files"
+PORTVAR = "port"
+
+# F u n c t i o n s
+
+def is_test(d):
+    if not d.startswith(FPREFIX):
+        return False
+    return os.path.isdir(os.path.join(TESTDIR, d)) and os.path.isdir(os.path.join(TESTDIR, d, FILESDIR))
+
+def is_subclass(a, b):
+    try:
+        return issubclass(a, b)
+    except TypeError:
+        return False
+
+# M a i n   P r o g r a m
+
+# Parse arguments
+parser = ArgumentParser(prog=sys.argv[0], usage="%(prog)s [options]")
+parser.add_argument("-p", "--port", default=8080, help="port to use (default: 8080)")
+args = parser.parse_args(sys.argv[1:])
+
+# Reject attempts to run out of directory
+if not os.path.isdir(TESTDIR):
+    sys.stderr.write("{0}: {1!r} - no such directory\n".format(MYNAME, TESTDIR))
+    sys.exit(2)
+
+# Get the log file name and ensure the log file is cleared
+from tests import LOGNAME, LOGGERNAME
+open(LOGNAME, "w").close()
+
+# Make a logger object for ServerFixture to use
+mylog = logging.getLogger(LOGGERNAME)
+handler = logging.FileHandler(LOGNAME)
+handler.setFormatter(
+    logging.Formatter(fmt="%(asctime)s - %(levelname)s: %(message)s"))
+mylog.addHandler(handler)
+mylog.setLevel(logging.INFO)
+
+# Locate all fixtures and operate on them
+failures = 0
+fixtures = sorted(filter(is_test, os.listdir(TESTDIR)))
+if not fixtures:
+    sys.stderr.write("{0}: warning - no fixtures found\n".format(MYNAME))
+for fixture in fixtures:
+    name = "{0}.{1}".format(TESTDIR, fixture)
+    sys.stderr.write("{0}: running {1} ...\n".format(MYNAME, name))
+    module = importlib.import_module(name)
+    directory = os.path.join(TESTDIR, fixture, FILESDIR)
+    for i in dir(module):
+        v = getattr(module, i)
+        if is_subclass(v, unittest.TestCase):
+            setattr(v, FILESVAR, directory)
+            setattr(v, PORTVAR, args.port)
+    suite = unittest.defaultTestLoader.loadTestsFromModule(module)
+    result = unittest.TextTestRunner(verbosity=1).run(suite)
+    if result.wasSuccessful():
+        sys.stderr.write("{0}: {1} passed\n".format(MYNAME, name))
+    else:
+        sys.stderr.write("{0}: {1} failed\n".format(MYNAME, name))
+        failures += 1
+
+# AMF...
+if failures:
+    sys.stderr.write("{0}: {1} suite{2} failed\n".format(
+        MYNAME,
+        failures,
+        "" if failures == 1 else "s"))
+
+sys.stdout.write("Note: server log output is in {0!r}.\n".format(LOGNAME))
+sys.exit(1 if failures else 0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+
+# I m p o r t s
+
+import unittest
+import tincan
+import logging
+import subprocess
+import time
+
+# V a r i a b l e s
+
+LOGGERNAME = "run-tests-server"
+LOGNAME = "run-tests-server.log"
+
+# F u n c t i o n s
+
+def _flush(logger):
+    for handler in logger.handlers:
+        handler.flush()
+
+# C l a s s e s
+
+class ServerFixture(unittest.TestCase):
+    """
+    This tests if we can launch something successfully or not. It is intended
+    to have but a single test case, which checks that cls.errors is 0.
+    """
+    files = None
+    port = None
+    app = None
+    errors = None
+
+    @classmethod
+    def setUpClass(cls):
+        mylog = logging.getLogger(LOGGERNAME)
+        mylog.info("*** BEGIN %s.%s", cls.__module__, cls.__name__)
+        cls.app, cls.errors = tincan.launch(fsroot=cls.files, logger=mylog)
+        mylog.info("*** END %s.%s", cls.__module__, cls.__name__)
+
+    @classmethod
+    def tearDownClass(cls):
+        pass
+
+class RoutesFixture(unittest.TestCase):
+    """
+    This is for testing routes. It assumes the server will start and run,
+    and runs that server in a separate process. It can (and often will) have
+    multiple test cases, which will use requests and html.parser to verify
+    the output is as expected.
+    """
+    files = None
+    port = None
+    server = None
+    logger = None
+
+    @classmethod
+    def setUpClass(cls):
+        mylog = logging.getLogger(LOGGERNAME)
+        mylog.info("*** BEGIN %s.%s", cls.__module__, cls.__name__)
+        _flush(mylog)
+        cls.logger = open(LOGNAME, "a")
+        cls.server = subprocess.Popen(
+            [ "bin/launch", "--port={0}".format(cls.port), cls.files, ],
+            stdin=subprocess.DEVNULL, stdout=cls.logger, stderr=cls.logger)
+        time.sleep(3)  # xxx
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.server.terminate()
+        cls.server.wait()
+        cls.logger.close()
+        mylog = logging.getLogger(LOGGERNAME)
+        mylog.info("*** END %s.%s", cls.__module__, cls.__name__)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_01_bad_errors/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,10 @@
+# I m p o r t s
+
+import os, sys
+from .. import ServerFixture
+
+# C l a s s e s
+
+class Fixture01(ServerFixture):
+    def runTest(self):
+        self.assertGreater(self.errors, 0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_01_bad_errors/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_01_bad_errors/files/bad_errors.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,1 @@
+#errors boogers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_02_good_errors/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,20 @@
+# I m p o r t s
+
+import os, sys
+import requests
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(ServerFixture):
+    # There should be no errors when we load this.
+    def runTest(self):
+        self.assertEqual(self.errors, 0)
+
+class Fixture02(RoutesFixture):
+    # Request something that doesn't exist. We should get our custom error
+    # page.
+    def test_01_404(self):
+        response = requests.get("http://localhost:{0}/boogers/".format(self.port))
+        self.assertEqual(response.status_code, 404)
+        self.assertTrue("I/O, I/O, it’s off to disk I go!" in response.text)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_02_good_errors/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_02_good_errors/files/good_errors_1.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,17 @@
+#rem test the case with an explicit error number
+#errors 404
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>${error.status_line}</title>
+  </head>
+  <body>
+    <h1>${error.status_line}</h1>
+<pre>I/O, I/O, it’s off to disk I go!
+You typed some junk,
+and now it’s stunk.
+I/O, I/O!
+</pre>
+    <p>It seems the resource at <kbd>${request.url}</kbd> can not be found.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_02_good_errors/files/good_errors_2.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,13 @@
+#rem Test the case with multiple error numbers
+#errors 500 503
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>${error.status_line}</title>
+  </head>
+  <body>
+    <h1>${error.status_line}</h1>
+    <p>How embarrassing! It seems there was an internal error serving
+    <kbd>${request.url}</kbd></p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_02a_good_errors/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,20 @@
+# I m p o r t s
+
+import os, sys
+import requests
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(ServerFixture):
+    # There should be no errors when we load this.
+    def runTest(self):
+        self.assertEqual(self.errors, 0)
+
+class Fixture02(RoutesFixture):
+    # Request something that doesn't exist. We should get our custom error
+    # page.
+    def test_01_404(self):
+        response = requests.get("http://localhost:{0}/boogers/".format(self.port))
+        self.assertEqual(response.status_code, 404)
+        self.assertTrue("I/O, I/O, it’s off to disk I go!" in response.text)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_02a_good_errors/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_02a_good_errors/files/good_errors.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,17 @@
+#rem test the case without any explicit error number
+#errors
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>${error.status_line}</title>
+  </head>
+  <body>
+    <h1>${error.status_line}</h1>
+<pre>I/O, I/O, it’s off to disk I go!
+You typed some junk,
+and now it’s stunk.
+I/O, I/O!
+</pre>
+    <p>It seems the resource at <kbd>${request.url}</kbd> can not be found.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_02b_good_errors/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,20 @@
+# I m p o r t s
+
+import os, sys
+import requests
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(ServerFixture):
+    # There should be no errors when we load this.
+    def runTest(self):
+        self.assertEqual(self.errors, 0)
+
+class Fixture02(RoutesFixture):
+    # Request something that doesn't exist. We should get our custom error
+    # page.
+    def test_01_404(self):
+        response = requests.get("http://localhost:{0}/boogers/".format(self.port))
+        self.assertEqual(response.status_code, 404)
+        self.assertTrue("I/O, I/O, it’s off to disk I go!" in response.text)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_02b_good_errors/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_02b_good_errors/files/good_errors.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,17 @@
+#rem test the case multiple error numbers
+#errors 400 401 402 403 404 500 501
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>${error.status_line}</title>
+  </head>
+  <body>
+    <h1>${error.status_line}</h1>
+<pre>I/O, I/O, it’s off to disk I go!
+You typed some junk,
+and now it’s stunk.
+I/O, I/O!
+</pre>
+    <p>It seems the resource at <kbd>${request.url}</kbd> can not be found.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_03_good_forward/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,32 @@
+# I m p o r t s
+
+import os, sys
+import requests
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(RoutesFixture):
+    # Same directory
+    def test_01_same(self):
+        response = requests.get("http://localhost:{0}/same.pspx".format(self.port))
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue("This is the original page." in response.text)
+
+    # Relative, with .
+    def test_02_relative_dot(self):
+        response = requests.get("http://localhost:{0}/relative.pspx".format(self.port))
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue("This is the original page." in response.text)
+
+    # Relative, with ..
+    def test_03_relative_dotdot(self):
+        response = requests.get("http://localhost:{0}/subdir/relative.pspx".format(self.port))
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue("This is the original page." in response.text)
+
+    # Absolute
+    def test_04_absolute(self):
+        response = requests.get("http://localhost:{0}/subdir/absolute.pspx".format(self.port))
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue("This is the original page." in response.text)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_03_good_forward/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_03_good_forward/files/original.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+#rem This is the original page. We don't ever actually request it
+#rem via its route; we access it via various forwards as tests.
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Original Page</title>
+  </head>
+  <body>
+    <h1>Original Page</h1>
+    <p>This is the original page.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_03_good_forward/files/relative.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+#rem Relative, using .
+#forward ./original.pspx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_03_good_forward/files/same.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+#rem No directory specifier should reference something in same directory.
+#forward original.pspx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_03_good_forward/files/subdir/absolute.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+#rem With an absolute specifier
+#forward /original.pspx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_03_good_forward/files/subdir/relative.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+#rem Relative, using ..
+#forward ../original.pspx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_04_bad_forward_too_many_dotdots/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+# I m p o r t s
+
+import os, sys
+import requests
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(ServerFixture):
+    # TinCan should choke on this.
+    def runTest(self):
+        self.assertGreater(self.errors, 0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_04_bad_forward_too_many_dotdots/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_04_bad_forward_too_many_dotdots/files/bad.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+#rem This should fail, too many ..'s.
+#forward ../original.pspx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_05_bad_forward_not_pspx/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+# I m p o r t s
+
+import os, sys
+import requests
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(ServerFixture):
+    # TinCan should choke on this.
+    def runTest(self):
+        self.assertGreater(self.errors, 0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_05_bad_forward_not_pspx/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_05_bad_forward_not_pspx/files/bad.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+#rem Should choke because it references a non-.pspx file.
+#forward boogers.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_06_bad_forward_missing/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+# I m p o r t s
+
+import os, sys
+import requests
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(ServerFixture):
+    # TinCan should choke on this.
+    def runTest(self):
+        self.assertGreater(self.errors, 0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_06_bad_forward_missing/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_06_bad_forward_missing/files/bad.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+#rem Should choke because it references a missing file
+#forward missing.pspx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_06_bad_forward_missing/files/good_errors_1.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,17 @@
+#rem test the case with an explicit error number
+#errors 404
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>${error.status_line}</title>
+  </head>
+  <body>
+    <h1>${error.status_line}</h1>
+<pre>I/O, I/O, it’s off to disk I go!
+You typed some junk,
+and now it’s stunk.
+I/O, I/O!
+</pre>
+    <p>It seems the resource at <kbd>${request.url}</kbd> can not be found.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_06_bad_forward_missing/files/good_errors_2.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,13 @@
+#rem Test the case with multiple error numbers
+#errors 500 503
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>${error.status_line}</title>
+  </head>
+  <body>
+    <h1>${error.status_line}</h1>
+    <p>How embarrassing! It seems there was an internal error serving
+    <kbd>${request.url}</kbd></p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_07_bad_forward_garbage/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+# I m p o r t s
+
+import os, sys
+import requests
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(ServerFixture):
+    # TinCan should choke on this.
+    def runTest(self):
+        self.assertGreater(self.errors, 0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_07_bad_forward_garbage/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_07_bad_forward_garbage/files/bad.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+#rem References a garbage file, should choke
+#forward garbage.dat
Binary file tests/suite_07_bad_forward_garbage/files/garbage.dat has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_08_error_error/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,14 @@
+# I m p o r t s
+
+import os, sys
+import requests
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(RoutesFixture):
+    # One #error page should be able to #forward to another.
+    def runTest(self):
+        response = requests.get("http://localhost:{0}/boogers/".format(self.port))
+        self.assertEqual(response.status_code, 404)
+        self.assertTrue("How embarrassing!" in response.text)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_08_error_error/files/404.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,3 @@
+#rem Test of forwarding to another #errors page
+#errors 404
+#forward 500.pspx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_08_error_error/files/500.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,13 @@
+#rem This page will be displayed when something cannot be found.
+#errors 500
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>${error.status_line}</title>
+  </head>
+  <body>
+    <h1>${error.status_line}</h1>
+    <p>How embarrassing! It seems there was an internal error serving
+    <kbd>${request.url}</kbd></p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_08_error_error/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_09_bad_forward_norm_error/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+# I m p o r t s
+
+import os, sys
+import requests
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(ServerFixture):
+    # TinCan should choke on this.
+    def runTest(self):
+        self.assertGreater(self.errors, 0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_09_bad_forward_norm_error/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_09_bad_forward_norm_error/files/error.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,13 @@
+#rem This page will be displayed when something cannot be found.
+#errors 500
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>${error.status_line}</title>
+  </head>
+  <body>
+    <h1>${error.status_line}</h1>
+    <p>How embarrassing! It seems there was an internal error serving
+    <kbd>${request.url}</kbd></p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_09_bad_forward_norm_error/files/normal.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,3 @@
+#rem A normal page that tries to #forward to an #error page.
+#rem This is an error and should be rejected.
+#forward error.pspx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_10_bad_forward_error_norm/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+# I m p o r t s
+
+import os, sys
+import requests
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(ServerFixture):
+    # TinCan should choke on this.
+    def runTest(self):
+        self.assertGreater(self.errors, 0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_10_bad_forward_error_norm/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_10_bad_forward_error_norm/files/error.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,4 @@
+#rem An #errors page that tries to #forward to a normal one.
+#rem This is an error and should be rejected.
+#errors
+#forward normal.pspx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_10_bad_forward_error_norm/files/normal.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,11 @@
+#rem A normal page, should get a route created for it.
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello</title>
+  </head>
+  <body>
+    <h1>Hello, World</h1>
+    <p>This page was called on the route ${page.request.environ['PATH_INFO']}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_11_hidden/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,24 @@
+# I m p o r t s
+
+import os, sys
+import requests
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(RoutesFixture):
+    # Same directory
+    def test_01_hidden(self):
+        response = requests.get("http://localhost:{0}/hidden.pspx".format(self.port))
+        self.assertEqual(response.status_code, 404)
+
+    # Relative, with .
+    def test_02_not_hidden(self):
+        response = requests.get("http://localhost:{0}/nothidden.pspx".format(self.port))
+        self.assertEqual(response.status_code, 200)
+
+    # Relative, with ..
+    def test_03_forward(self):
+        response = requests.get("http://localhost:{0}/forward.pspx".format(self.port))
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue("Hello, World (Hidden)" in response.text)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_11_hidden/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_11_hidden/files/forward.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,3 @@
+#rem This forwards to a hidden page. Because this page is not itself
+#rem hidden, it should have a route made for it.
+#forward hidden.pspx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_11_hidden/files/hidden.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+#rem This is hidden, so should not have a route made for it.
+#hidden
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello (Hidden)</title>
+  </head>
+  <body>
+    <h1>Hello, World (Hidden)</h1>
+    <p>This page was called on the route ${page.request.environ['PATH_INFO']}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_11_hidden/files/nothidden.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,11 @@
+#rem Normal, unhidden page, should have a route made for it.
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello</title>
+  </head>
+  <body>
+    <h1>Hello, World (Not Hidden)</h1>
+    <p>This page was called on the route ${page.request.environ['PATH_INFO']}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_12_methods/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,35 @@
+# I m p o r t s
+
+import os, sys
+import requests
+import urllib.parse
+from bs4 import BeautifulSoup
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(RoutesFixture):
+    def _doform(self, page):
+        url = "http://localhost:{0}/{1}".format(self.port, page)
+        response = requests.get(url)
+        self.assertEqual(response.status_code, 200)
+        soup = BeautifulSoup(response.text, 'html.parser')
+        self.assertIsNotNone(soup.find(
+            "input", attrs={"type": "text", "name": "name"}))
+        self.assertIsNotNone(soup.find(
+            "input", attrs={"type": "submit", "value": "Submit"}))
+        form = soup.find("form")
+        self.assertIsNotNone(form)
+        action_url = urllib.parse.urljoin(url, form.get("action", page))
+        response = requests.post(url, {"name": "Barney Dinosaur"})
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue("Hello, Barney" in response.text)
+        self.assertTrue("Pleased to meet you!" in response.text)
+
+    # Methods with their standard, uppercase names
+    def test_01_methods(self):
+        self._doform("name.pspx")
+
+    # Methods with alternate capitalizations. This also tests #python
+    def test_02_methods_lc(self):
+        self._doform("name_lc.pspx")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_12_methods/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_12_methods/files/WEB-INF/lib/jsdict.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+class JSDict(dict):
+    """
+    Make a dict that acts something like a JavaScript object, in that we can
+    use both x["name"] and x.name to access something. Note that the latter
+    method fails for keys like x["get"] that duplicate dict methods, and keys
+    like x["1"] which are not legal Python identifiers.
+    """
+    def __getattr__(self, name):
+        return self[name]
+
+    def __setattr__(self, name, value):
+        self[name] = value
+
+    def __delattr__(self, name):
+        del self[name]
+
+    @classmethod
+    def from_dict(cls, d):
+        return cls(d)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_12_methods/files/name.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,20 @@
+#rem A page that accepts both GET and POST requests
+#methods GET POST
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Form Test</title>
+  </head>
+  <body>
+    <h1>Form Test</h1>
+    <h2>Enter Your Name Below</h2>
+    <form method="post">
+      <input type="text" name="name"/>
+      <input type="submit" value="Submit"/>
+    </form>
+    <if tal:condition="message is not None" tal:omit-tag="True">
+      <h2 tal:content="message.subject"></h2>
+      <p tal:attributes="style message.style" tal:content="message.body"></p>
+    </if>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_12_methods/files/name.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,28 @@
+# It is *technically* an HTTP violation to use POST for an idempotent
+# page like this, but it is a good simple test.
+
+from tincan import Page
+from jsdict import JSDict
+
+class Name(Page):
+    def handle(self):
+        self._error_message = JSDict({"subject": "Error", "style": "color: red;"})
+        if "name" not in self.request.forms:
+            if self.request.method == "GET":
+                self.message = None
+            else:
+                self.message = self.error("This should not happen!")
+        else:
+            name = self.request.forms["name"]
+            if name.strip() == "":
+                self.message = self.error("Please enter your name above.")
+            else:
+                self.message = JSDict({
+                    "subject": "Hello, {0}".format(name.split()[0]),
+                    "style": None,
+                    "body": "Pleased to meet you!"
+                })
+
+    def error(self, message):
+        self._error_message.body = message
+        return self._error_message
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_12_methods/files/name_lc.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,22 @@
+#rem A page that accepts both GET and POST requests (non UC version,
+#rem to verify case insensitivity of request methods).
+#methods Get post
+#python name.py
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Form Test</title>
+  </head>
+  <body>
+    <h1>Form Test</h1>
+    <h2>Enter Your Name Below</h2>
+    <form method="post">
+      <input type="text" name="name"/>
+      <input type="submit" value="Submit"/>
+    </form>
+    <if tal:condition="message is not None" tal:omit-tag="True">
+      <h2 tal:content="message.subject"></h2>
+      <p tal:attributes="style message.style" tal:content="message.body"></p>
+    </if>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_13_bad_python/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,10 @@
+# I m p o r t s
+
+import os, sys
+from .. import ServerFixture
+
+# C l a s s e s
+
+class Fixture01(ServerFixture):
+    def runTest(self):
+        self.assertEqual(self.errors, 5)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_13_bad_python/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
Binary file tests/suite_13_bad_python/files/basura.py has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_13_bad_python/files/garbage.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+#rem Should fail because #python file is complete garbage.
+#python basura.py
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello</title>
+  </head>
+  <body>
+    <h1>Hello Again, World</h1>
+    <p>The current time on the server is ${time}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_13_bad_python/files/garbage2.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,11 @@
+#rem No #python, but should also fail because source is garbage.
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello</title>
+  </head>
+  <body>
+    <h1>Hello Again, World</h1>
+    <p>The current time on the server is ${time}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_13_bad_python/files/garbage2.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,1 @@
+This is not a vaild python file!
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_13_bad_python/files/hello.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+#rem This is a basic "hello, world" test
+#python hello.py
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello</title>
+  </head>
+  <body>
+    <h1>Hello Again, World</h1>
+    <p>The current time on the server is ${time}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_13_bad_python/files/hello.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,6 @@
+import time
+import tincan
+
+class Hello2(tincan.Page):
+    def handle(self):
+        self.time = time.ctime()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_13_bad_python/files/missing.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+#rem Should fail; file is missing
+#python boogers.py
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello</title>
+  </head>
+  <body>
+    <h1>Hello Again, World</h1>
+    <p>The current time on the server is ${time}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_13_bad_python/files/not_py.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+#rem Should fail; #python file doesn't end in .py
+#python hello.pspx
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello</title>
+  </head>
+  <body>
+    <h1>Hello Again, World</h1>
+    <p>The current time on the server is ${time}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_13_bad_python/files/too_many_dotdots.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+#rem Should make load choke; too many ..'s.
+#python ../files/hello.py
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello</title>
+  </head>
+  <body>
+    <h1>Hello Again, World</h1>
+    <p>The current time on the server is ${time}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_14_bad_template/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,10 @@
+# I m p o r t s
+
+import os, sys
+from .. import ServerFixture
+
+# C l a s s e s
+
+class Fixture01(ServerFixture):
+    def runTest(self):
+        self.assertEqual(self.errors, 5)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_14_bad_template/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
Binary file tests/suite_14_bad_template/files/garbage.pspx has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_14_bad_template/files/garbage2.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,13 @@
+#rem Refers to a #template that's pure garbage.
+#template garbage.pspx
+<!-- This will be ignored -->
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello</title>
+  </head>
+  <body>
+    <h1>Hello Again, World</h1>
+    <p>The current time on the server is ${time}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_14_bad_template/files/hello.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+#rem This is a basic "hello, world" test
+#python hello.py
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello</title>
+  </head>
+  <body>
+    <h1>Hello Again, World</h1>
+    <p>The current time on the server is ${time}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_14_bad_template/files/hello.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,6 @@
+import time
+import tincan
+
+class Hello2(tincan.Page):
+    def handle(self):
+        self.time = time.ctime()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_14_bad_template/files/missing.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+#rem Should fail; file is missing
+#template boogers.pspx
Binary file tests/suite_14_bad_template/files/not_pspx.dat has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_14_bad_template/files/not_pspx.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+#rem Should fail; #python file doesn't end in .py
+#template not_pspx.dat
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello</title>
+  </head>
+  <body>
+    <h1>Hello Again, World</h1>
+    <p>The current time on the server is ${time}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_14_bad_template/files/too_many_dotdots.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+#rem Should make load choke; too many ..'s.
+#template ../files/hello.pspx
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello</title>
+  </head>
+  <body>
+    <h1>Hello Again, World</h1>
+    <p>The current time on the server is ${time}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_15_prog_forward/__init__.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,21 @@
+# I m p o r t s
+
+import os, sys
+import requests
+from .. import ServerFixture, RoutesFixture
+
+# C l a s s e s
+
+class Fixture01(RoutesFixture):
+    def test_01_good(self):
+        response = requests.get("http://localhost:{0}/good.pspx".format(self.port))
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue("The current time on the server is" in response.text)
+
+    def test_02_bad(self):
+        response = requests.get("http://localhost:{0}/bad.pspx".format(self.port))
+        self.assertEqual(response.status_code, 404)
+        
+    def test_03_bad(self):
+        response = requests.get("http://localhost:{0}/bad2.pspx".format(self.port))
+        self.assertEqual(response.status_code, 500)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_15_prog_forward/files/WEB-INF/.hidden	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,2 @@
+This hidden file to force Mercurial to always commit this needed
+directory, even when it is otherwise empty.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_15_prog_forward/files/bad.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,14 @@
+#rem This page's code-behind has a bad programmatic forward, so
+#rem it should fail with a 500 error.
+<!DOCTYPE html>
+<!-- This content will end up getting ignored. -->
+<html>
+  <head>
+    <title>This Doesn't Matter</title>
+  </head>
+  <body>
+    <h1>This doesn't matter</h1>
+    <p>This content will never get rendered due to a programmatic
+       forward in the code-behind.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_15_prog_forward/files/bad.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,6 @@
+import tincan
+
+class Hello2(tincan.Page):
+    def handle(self):
+        self.time = "This does not matter."
+        self.request.app.forward("/boogers.pspx")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_15_prog_forward/files/bad2.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,14 @@
+#rem This page's code-behind has a bad programmatic forward, so
+#rem it should fail with a 500 error.
+<!DOCTYPE html>
+<!-- This content will end up getting ignored. -->
+<html>
+  <head>
+    <title>This Doesn't Matter</title>
+  </head>
+  <body>
+    <h1>This doesn't matter</h1>
+    <p>This content will never get rendered due to a programmatic
+       forward in the code-behind.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_15_prog_forward/files/bad2.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,6 @@
+import tincan
+
+class Hello2(tincan.Page):
+    def handle(self):
+        self.time = "This does not matter."
+        self.request.app.forward("/bad2.pspx")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_15_prog_forward/files/good.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,14 @@
+#rem This page's code-behind has a valid programmatic forward, so
+#rem it should succeed.
+<!DOCTYPE html>
+<!-- This content will end up getting ignored. -->
+<html>
+  <head>
+    <title>This Doesn't Matter</title>
+  </head>
+  <body>
+    <h1>This doesn't matter</h1>
+    <p>This content will never get rendered due to a programmatic
+       forward in the code-behind.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_15_prog_forward/files/good.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,6 @@
+import tincan
+
+class Hello2(tincan.Page):
+    def handle(self):
+        self.time = "This does not matter."
+        self.request.app.forward("/hello.pspx")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_15_prog_forward/files/hello.pspx	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,12 @@
+#rem This is a basic "hello, world" test
+#python hello.py
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello</title>
+  </head>
+  <body>
+    <h1>Hello Again, World</h1>
+    <p>The current time on the server is ${time}.</p>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/suite_15_prog_forward/files/hello.py	Mon Jul 15 13:16:31 2019 -0700
@@ -0,0 +1,6 @@
+import time
+import tincan
+
+class Hello2(tincan.Page):
+    def handle(self):
+        self.time = time.ctime()