From 32068806731ecae8e7bf5ca3115f766fe6949d69 Mon Sep 17 00:00:00 2001 From: James Kasten Date: Sun, 19 Jul 2015 16:48:27 -0700 Subject: [PATCH] add complex parsing tests --- .../letsencrypt_apache/parser.py | 21 ++-- .../tests/complex_parsing_test.py | 101 ++++++++++++++++++ .../tests/configurator_test.py | 27 ++++- .../letsencrypt_apache/tests/dvsni_test.py | 2 +- .../letsencrypt_apache/tests/parser_test.py | 25 ++--- .../testdata/complex_parsing/apache2.conf | 4 +- .../complex_parsing/conf-enabled/dummy.conf | 9 ++ .../tests/testdata/complex_parsing/test.conf | 1 - .../complex_parsing/test_fnmatch.conf | 1 + .../complex_parsing/test_variables.conf | 65 +++++++++++ .../letsencrypt_apache/tests/util.py | 31 +++++- 11 files changed, 253 insertions(+), 34 deletions(-) create mode 100644 letsencrypt-apache/letsencrypt_apache/tests/complex_parsing_test.py create mode 100644 letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf delete mode 100644 letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/test.conf create mode 100644 letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/test_fnmatch.conf create mode 100644 letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/test_variables.conf diff --git a/letsencrypt-apache/letsencrypt_apache/parser.py b/letsencrypt-apache/letsencrypt_apache/parser.py index a0bc6fd12..48234bfe5 100644 --- a/letsencrypt-apache/letsencrypt_apache/parser.py +++ b/letsencrypt-apache/letsencrypt_apache/parser.py @@ -126,7 +126,7 @@ class ApacheParser(object): return stdout - def _filter_args_num(self, matches, args): # pylint: disable=no-self-use + def filter_args_num(self, matches, args): # pylint: disable=no-self-use """Filter out directives with specific number of arguments. This function makes the assumption that all related arguments are given @@ -142,16 +142,16 @@ class ApacheParser(object): """ filtered = [] if args == 1: - for i in range(matches): + for i in range(len(matches)): if matches[i].endswith("/arg"): filtered.append(matches[i][:-4]) else: - for i in range(matches): - if matches[i].endswith("/arg[%d]", args): + for i in range(len(matches)): + if matches[i].endswith("/arg[%d]" % args): # Make sure we don't cause an IndexError (end of list) # Check to make sure arg + 1 doesn't exist if (i == (len(matches) - 1) or - not matches[i + 1].endswith("/arg[%d]" % args + 1)): + not matches[i + 1].endswith("/arg[%d]" % (args + 1))): filtered.append(matches[i][:-len("/arg[%d]" % args)]) return filtered @@ -340,11 +340,16 @@ class ApacheParser(object): while last_match_idx != -1: # Check args end_of_if = match_l.find("/", last_match_idx) + # This should be aug.get (vars are not used e.g. parser.aug_get) expression = self.aug.get(match[:end_of_if] + "/arg") - expected = not expression.startswith("!") - if expected != (expression in filter_[1]): - return False + if expression.startswith("!"): + # Strip off "!" + if expression[1:] in filter_[1]: + return False + else: + if expression not in filter_[1]: + return False last_match_idx = match_l.find(filter_[0], end_of_if) diff --git a/letsencrypt-apache/letsencrypt_apache/tests/complex_parsing_test.py b/letsencrypt-apache/letsencrypt_apache/tests/complex_parsing_test.py new file mode 100644 index 000000000..7281061e4 --- /dev/null +++ b/letsencrypt-apache/letsencrypt_apache/tests/complex_parsing_test.py @@ -0,0 +1,101 @@ +"""Tests for letsencrypt_apache.parser.""" +import os +import shutil +import unittest + +from letsencrypt_apache.tests import util + + +class ComplexParserTest(util.ParserTest): + """Apache Parser Test.""" + + def setUp(self): # pylint: disable=arguments-differ + super(ComplexParserTest, self).setUp( + "complex_parsing", "complex_parsing") + + self.setup_variables() + # This needs to happen after due to setup_variables not being run + # until after + self.parser._init_modules() # pylint: disable=protected-access + + def tearDown(self): + shutil.rmtree(self.temp_dir) + shutil.rmtree(self.config_dir) + shutil.rmtree(self.work_dir) + + def setup_variables(self): + """Set up variables for parser.""" + self.parser.variables.update( + { + "COMPLEX": "", + "tls_port": "1234", + "fnmatch_filename": "test_fnmatch.conf", + } + ) + + def test_filter_args_num(self): + matches = self.parser.find_dir("TestArgsDirective") + + self.assertEqual(len(self.parser.filter_args_num(matches, 1)), 3) + self.assertEqual(len(self.parser.filter_args_num(matches, 2)), 2) + self.assertEqual(len(self.parser.filter_args_num(matches, 3)), 1) + + def test_basic_variable_parsing(self): + matches = self.parser.find_dir("TestVariablePort") + + self.assertEqual(len(matches), 1) + self.assertEqual(self.parser.get_arg(matches[0]), "1234") + + def test_basic_ifdefine(self): + self.assertEqual(len(self.parser.find_dir("VAR_DIRECTIVE")), 2) + self.assertEqual(len(self.parser.find_dir("INVALID_VAR_DIRECTIVE")), 0) + + def test_basic_ifmodule(self): + self.assertEqual(len(self.parser.find_dir("MOD_DIRECTIVE")), 2) + self.assertEqual( + len(self.parser.find_dir("INVALID_MOD_DIRECTIVE")), 0) + + def test_nested(self): + self.assertEqual(len(self.parser.find_dir("NESTED_DIRECTIVE")), 3) + self.assertEqual( + len(self.parser.find_dir("INVALID_NESTED_DIRECTIVE")), 0) + + + def test_load_modules(self): + """If only first is found, there is bad variable parsing.""" + self.assertTrue("status_module" in self.parser.modules) + self.assertTrue("mod_status.c" in self.parser.modules) + + # This is in an IfDefine + self.assertTrue("ssl_module" in self.parser.modules) + self.assertTrue("mod_ssl.c" in self.parser.modules) + + def verify_fnmatch(self, arg, hit=True): + """Test if Include was correctly parsed.""" + from letsencrypt_apache import parser + self.parser.add_dir(parser.get_aug_path(self.parser.loc["default"]), + "Include", [arg]) + if hit: + self.assertTrue(self.parser.find_dir("FNMATCH_DIRECTIVE")) + else: + self.assertFalse(self.parser.find_dir("FNMATCH_DIRECTIVE")) + + def test_include(self): + self.verify_fnmatch("test_fnmatch.?onf") + + def test_include_complex(self): + self.verify_fnmatch("../complex_parsing/[te][te]st_*.?onf") + + def test_include_fullpath(self): + self.verify_fnmatch(os.path.join(self.config_path, "test_fnmatch.conf")) + + def test_include_variable(self): + self.verify_fnmatch("../complex_parsing/${fnmatch_filename}") + + def test_include_missing(self): + # This should miss + self.verify_fnmatch("test_*.onf", False) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py index 111e82a2d..e68b21945 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py @@ -22,7 +22,7 @@ from letsencrypt_apache.tests import util class TwoVhost80Test(util.ApacheTest): """Test two standard well-configured HTTP vhosts.""" - def setUp(self): + def setUp(self): # pylint: disable=arguments-differ super(TwoVhost80Test, self).setUp() self.config = util.get_apache_configurator( @@ -137,6 +137,18 @@ class TwoVhost80Test(util.ApacheTest): self.assertEqual(configurator.get_file_path(loc_chain[0]), self.vh_truth[1].filep) + def test_deploy_cert_invalid_vhost(self): + self.config.parser.modules.add("ssl_module") + mock_find = mock.MagicMock() + mock_find.return_value = [] + self.config.parser.find_dir = mock_find + + # Get the default 443 vhost + self.config.assoc["random.demo"] = self.vh_truth[1] + self.assertRaises( + errors.PluginError, self.config.deploy_cert, "random.demo", + "example/cert.pem", "example/key.pem", "example/cert_chain.pem") + def test_is_name_vhost(self): addr = obj.Addr.fromstring("*:80") self.assertTrue(self.config.is_name_vhost(addr)) @@ -148,6 +160,19 @@ class TwoVhost80Test(util.ApacheTest): self.assertTrue(self.config.parser.find_dir( "NameVirtualHost", "*:443")) + def test_prepare_server_https(self): + self.config.parser.modules.add("ssl_module") + mock_find = mock.Mock() + mock_add_dir = mock.Mock() + mock_find.return_value = [] + + # This will test the Add listen + self.config.parser.find_dir = mock_find + self.config.parser.add_dir_to_ifmodssl = mock_add_dir + + self.config.prepare_server_https("443") + self.assertTrue(mock_add_dir.called) + def test_make_vhost_ssl(self): ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) diff --git a/letsencrypt-apache/letsencrypt_apache/tests/dvsni_test.py b/letsencrypt-apache/letsencrypt_apache/tests/dvsni_test.py index b6f9bc5e0..b0aec4f8a 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/dvsni_test.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/dvsni_test.py @@ -17,7 +17,7 @@ class DvsniPerformTest(util.ApacheTest): achalls = common_test.DvsniTest.achalls - def setUp(self): + def setUp(self): # pylint: disable=arguments-differ super(DvsniPerformTest, self).setUp() config = util.get_apache_configurator( diff --git a/letsencrypt-apache/letsencrypt_apache/tests/parser_test.py b/letsencrypt-apache/letsencrypt_apache/tests/parser_test.py index 3a74055ad..49575e977 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/parser_test.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/parser_test.py @@ -1,34 +1,21 @@ """Tests for letsencrypt_apache.parser.""" import os import shutil -import sys import unittest import augeas import mock -import zope.component from letsencrypt import errors -from letsencrypt.display import util as display_util from letsencrypt_apache.tests import util -class ApacheParserTest(util.ApacheTest): +class BasicParserTest(util.ParserTest): """Apache Parser Test.""" - def setUp(self): - super(ApacheParserTest, self).setUp() - - zope.component.provideUtility(display_util.FileDisplay(sys.stdout)) - - from letsencrypt_apache.parser import ApacheParser - self.aug = augeas.Augeas( - flags=augeas.Augeas.NONE | augeas.Augeas.NO_MODL_AUTOLOAD) - with mock.patch("letsencrypt_apache.parser.ApacheParser." - "update_runtime_variables"): - self.parser = ApacheParser( - self.aug, self.config_path, self.ssl_options, "dummy_ctl_path") + def setUp(self): # pylint: disable=arguments-differ + super(BasicParserTest, self).setUp() def tearDown(self): shutil.rmtree(self.temp_dir) @@ -61,6 +48,10 @@ class ApacheParserTest(util.ApacheTest): self.assertEqual(len(test), 1) self.assertEqual(len(test2), 3) + def test_filter_args_num(self): + # TODO: TEST 2, TEST 1 + pass + def test_add_dir(self): aug_default = "/files" + self.parser.loc["default"] self.parser.add_dir(aug_default, "AddDirective", "test") @@ -115,7 +106,7 @@ class ApacheParserTest(util.ApacheTest): class ParserInitTest(util.ApacheTest): - def setUp(self): + def setUp(self): # pylint: disable=arguments-differ super(ParserInitTest, self).setUp() self.aug = augeas.Augeas( flags=augeas.Augeas.NONE | augeas.Augeas.NO_MODL_AUTOLOAD) diff --git a/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/apache2.conf b/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/apache2.conf index a1f4e05fc..b7b6a9be2 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/apache2.conf +++ b/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/apache2.conf @@ -46,8 +46,8 @@ IncludeOptional sites-enabled/*.conf Define COMPLEX Define tls_port 1234 -Define example_path1 Documents/root +Define fnmatch_filename test_fnmatch.conf -Include test.conf +Include test_variables.conf # vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf b/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf new file mode 100644 index 000000000..1e5307780 --- /dev/null +++ b/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf @@ -0,0 +1,9 @@ +# 3 - one arg directives +# 2 - two arg directives +# 1 - three arg directives +TestArgsDirective one_arg +TestArgsDirective one_arg two_arg +TestArgsDirective one_arg +TestArgsDirective one_arg two_arg +TestArgsDirective one_arg two_arg three_arg +TestArgsDirective one_arg diff --git a/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/test.conf b/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/test.conf deleted file mode 100644 index f724756d5..000000000 --- a/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/test.conf +++ /dev/null @@ -1 +0,0 @@ -TESTDIRECTIVE ${tls_port} diff --git a/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/test_fnmatch.conf b/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/test_fnmatch.conf new file mode 100644 index 000000000..4e6b84edf --- /dev/null +++ b/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/test_fnmatch.conf @@ -0,0 +1 @@ +FNMATCH_DIRECTIVE Success diff --git a/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/test_variables.conf b/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/test_variables.conf new file mode 100644 index 000000000..a38191837 --- /dev/null +++ b/letsencrypt-apache/letsencrypt_apache/tests/testdata/complex_parsing/test_variables.conf @@ -0,0 +1,65 @@ +TestVariablePort ${tls_port} + +LoadModule status_module modules/mod_status.so + +# Basic IfDefine + + VAR_DIRECTIVE success + LoadModule ssl_module modules/mod_ssl.so + + + + INVALID_VAR_DIRECTIVE failure + + + + INVALID_VAR_DIRECTIVE failure + + + + VAR_DIRECTIVE failure + + + +# Basic IfModule + + MOD_DIRECTIVE Success + + + + INVALID_MOD_DIRECTIVE failure + + + + INVALID_MOD_DIRECTIVE failure + + + + MOD_DIRECTIVE Success + + +# Nested Tests + + + NESTED_DIRECTIVE success + + + NESTED_DIRECTIVE success + + + + INVALID_NESTED_DIRECTIVE failure + + + + + INVALID_NESTED_DIRECTIVE failure + + + INVALID_NESTED_DIRECTIVE failure + + + + NESTED_DIRECTIVE success + + diff --git a/letsencrypt-apache/letsencrypt_apache/tests/util.py b/letsencrypt-apache/letsencrypt_apache/tests/util.py index ae2f25b9f..8b54b08a4 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/util.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/util.py @@ -1,9 +1,14 @@ """Common utilities for letsencrypt_apache.""" import os import pkg_resources +import sys import unittest +import augeas import mock +import zope.component + +from letsencrypt.display import util as display_util from letsencrypt.plugins import common @@ -14,19 +19,20 @@ from letsencrypt_apache import obj class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods - def setUp(self): + def setUp(self, test_dir="debian_apache_2_4/two_vhost_80", + config_root="debian_apache_2_4/two_vhost_80/apache2"): + # pylint: disable=arguments-differ super(ApacheTest, self).setUp() self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( - test_dir="debian_apache_2_4/two_vhost_80", + test_dir=test_dir, pkg="letsencrypt_apache.tests") self.ssl_options = common.setup_ssl_options( self.config_dir, constants.MOD_SSL_CONF_SRC, constants.MOD_SSL_CONF_DEST) - self.config_path = os.path.join( - self.temp_dir, "debian_apache_2_4/two_vhost_80/apache2") + self.config_path = os.path.join(self.temp_dir, config_root) self.rsa256_file = pkg_resources.resource_filename( "letsencrypt.tests", os.path.join("testdata", "rsa256_key.pem")) @@ -34,6 +40,23 @@ class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods "letsencrypt.tests", os.path.join("testdata", "rsa256_key.pem")) +class ParserTest(ApacheTest): # pytlint: disable=too-few-public-methods + + def setUp(self, test_dir="debian_apache_2_4/two_vhost_80", + config_root="debian_apache_2_4/two_vhost_80/apache2"): + super(ParserTest, self).setUp(test_dir, config_root) + + zope.component.provideUtility(display_util.FileDisplay(sys.stdout)) + + from letsencrypt_apache.parser import ApacheParser + self.aug = augeas.Augeas( + flags=augeas.Augeas.NONE | augeas.Augeas.NO_MODL_AUTOLOAD) + with mock.patch("letsencrypt_apache.parser.ApacheParser." + "update_runtime_variables"): + self.parser = ApacheParser( + self.aug, self.config_path, self.ssl_options, "dummy_ctl_path") + + def get_apache_configurator( config_path, config_dir, work_dir, version=(2, 4, 7), conf=None): """Create an Apache Configurator with the specified options.