Merge ~chad.smith/cloud-init:fix/cent-6-jinja2 into cloud-init:master

Proposed by Chad Smith
Status: Work in progress
Proposed branch: ~chad.smith/cloud-init:fix/cent-6-jinja2
Merge into: cloud-init:master
Diff against target: 675 lines (+124/-118)
15 files modified
cloudinit/cmd/devel/__init__.py (+0/-25)
cloudinit/cmd/devel/render.py (+3/-3)
cloudinit/cmd/devel/tests/test_render.py (+28/-31)
cloudinit/cmd/query.py (+3/-3)
cloudinit/cmd/tests/test_clean.py (+1/-1)
cloudinit/cmd/tests/test_cloud_id.py (+6/-6)
cloudinit/cmd/tests/test_query.py (+34/-37)
cloudinit/cmd/tests/test_status.py (+1/-1)
cloudinit/helpers.py (+7/-0)
cloudinit/log.py (+7/-0)
cloudinit/sources/helpers/netlink.py (+4/-0)
cloudinit/stages.py (+7/-0)
cloudinit/templater.py (+19/-7)
tests/unittests/test_builtin_handlers.py (+2/-2)
tests/unittests/test_handler/test_schema.py (+2/-2)
Reviewer Review Type Date Requested Status
Ryan Harper Needs Fixing
Server Team CI bot continuous-integration Approve
Chad Smith Pending
Review via email: mp+360839@code.launchpad.net

Commit message

Centos 6: Fix jinja rendering and netlink on older python versions.

jinja2.runtime.implements_to_string is not available in older versions
of jinja2. This just catches that error separately from other
jinja support allowing the code to work.

Also fix netlink behavior stuct.unpack methods expect data as a string.

To improve testing and mocks, relocate the following:
 - read_cfg_paths to cloudinit.stages
 - addLogHandlerCLI to cloudinit.log

The testcase to recreate was:
 ./tools/run-container --package --source-package --unittest \
 --artifacts=./rpm/ centos/6

LP: #1795933

Description of the change

see commit message

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:b41878cb80997dafa51fd2f4c73d74bf82387f24
https://jenkins.ubuntu.com/server/job/cloud-init-ci/487/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/487/rebuild

review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

no mention of the addLogHanderCLI or read_cfg_paths move in commit message.
the rest of it appears to be test or PY26 only, so i'm good with that.

fix your commit message.

Revision history for this message
Ryan Harper (raharper) wrote :

I'd like to see this rebased against 18.3 since that's the last PY26 supported release.

review: Needs Fixing

Unmerged commits

b41878c... by Chad Smith

on PY26, subytraction is not attempted

5dc81f7... by Chad Smith

unittest fixups

c2f465e... by Chad Smith

always remember the mock

5f8561a... by Chad Smith

lint

e274a19... by Chad Smith

devel.read_cfg_paths -> stages and devel.addLogHandlerCLI -> log

4283e1e... by Chad Smith

struct.unpack requires str in py26

b7e7db8... by Chad Smith

need to mock leaked getuid for root unittest runs

31ef622... by Chad Smith

centos: assertraises context_manager.exception is an int on SystemExit instance

69677fc... by Scott Moser

Take Chad's good fix

9271323... by Scott Moser

Centos 6: Fix jinja rendering on older jinja versions.

jinja2.runtime.implements_to_string is not available in older versions
of jinja2. This just catches that error separately from other
jinja support allowing the code to work.

The testcase to recreate was:
  ./tools/run-container --source-package --package \
     --artifacts=./rpm/ centos/6

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/cloudinit/cmd/devel/__init__.py b/cloudinit/cmd/devel/__init__.py
index 3ae28b6..e69de29 100644
--- a/cloudinit/cmd/devel/__init__.py
+++ b/cloudinit/cmd/devel/__init__.py
@@ -1,25 +0,0 @@
1# This file is part of cloud-init. See LICENSE file for license information.
2
3"""Common cloud-init devel commandline utility functions."""
4
5
6import logging
7
8from cloudinit import log
9from cloudinit.stages import Init
10
11
12def addLogHandlerCLI(logger, log_level):
13 """Add a commandline logging handler to emit messages to stderr."""
14 formatter = logging.Formatter('%(levelname)s: %(message)s')
15 log.setupBasicLogging(log_level, formatter=formatter)
16 return logger
17
18
19def read_cfg_paths():
20 """Return a Paths object based on the system configuration on disk."""
21 init = Init(ds_deps=[])
22 init.read_cfg()
23 return init.paths
24
25# vi: ts=4 expandtab
diff --git a/cloudinit/cmd/devel/render.py b/cloudinit/cmd/devel/render.py
index 1bc2240..b93abd4 100755
--- a/cloudinit/cmd/devel/render.py
+++ b/cloudinit/cmd/devel/render.py
@@ -9,7 +9,7 @@ import sys
9from cloudinit.handlers.jinja_template import render_jinja_payload_from_file9from cloudinit.handlers.jinja_template import render_jinja_payload_from_file
10from cloudinit import log10from cloudinit import log
11from cloudinit.sources import INSTANCE_JSON_FILE, INSTANCE_JSON_SENSITIVE_FILE11from cloudinit.sources import INSTANCE_JSON_FILE, INSTANCE_JSON_SENSITIVE_FILE
12from . import addLogHandlerCLI, read_cfg_paths12from cloudinit import stages
1313
14NAME = 'render'14NAME = 'render'
1515
@@ -45,11 +45,11 @@ def handle_args(name, args):
4545
46 @return 0 on success, 1 on failure.46 @return 0 on success, 1 on failure.
47 """47 """
48 addLogHandlerCLI(LOG, log.DEBUG if args.debug else log.WARNING)48 log.addLogHandlerCLI(LOG, log.DEBUG if args.debug else log.WARNING)
49 if args.instance_data:49 if args.instance_data:
50 instance_data_fn = args.instance_data50 instance_data_fn = args.instance_data
51 else:51 else:
52 paths = read_cfg_paths()52 paths = stages.read_cfg_paths()
53 uid = os.getuid()53 uid = os.getuid()
54 redacted_data_fn = os.path.join(paths.run_dir, INSTANCE_JSON_FILE)54 redacted_data_fn = os.path.join(paths.run_dir, INSTANCE_JSON_FILE)
55 if uid == 0:55 if uid == 0:
diff --git a/cloudinit/cmd/devel/tests/test_render.py b/cloudinit/cmd/devel/tests/test_render.py
index 988bba0..19c05c3 100644
--- a/cloudinit/cmd/devel/tests/test_render.py
+++ b/cloudinit/cmd/devel/tests/test_render.py
@@ -7,7 +7,7 @@ from collections import namedtuple
7from cloudinit.cmd.devel import render7from cloudinit.cmd.devel import render
8from cloudinit.helpers import Paths8from cloudinit.helpers import Paths
9from cloudinit.sources import INSTANCE_JSON_FILE, INSTANCE_JSON_SENSITIVE_FILE9from cloudinit.sources import INSTANCE_JSON_FILE, INSTANCE_JSON_SENSITIVE_FILE
10from cloudinit.tests.helpers import CiTestCase, mock, skipUnlessJinja10from cloudinit.tests.helpers import CiTestCase, PY26, mock, skipUnlessJinja
11from cloudinit.util import ensure_dir, write_file11from cloudinit.util import ensure_dir, write_file
1212
1313
@@ -20,6 +20,7 @@ class TestRender(CiTestCase):
20 def setUp(self):20 def setUp(self):
21 super(TestRender, self).setUp()21 super(TestRender, self).setUp()
22 self.tmp = self.tmp_dir()22 self.tmp = self.tmp_dir()
23 self.add_patch('cloudinit.log.addLogHandlerCLI', 'm_logcli')
2324
24 def test_handle_args_error_on_missing_user_data(self):25 def test_handle_args_error_on_missing_user_data(self):
25 """When user_data file path does not exist, log an error."""26 """When user_data file path does not exist, log an error."""
@@ -28,8 +29,7 @@ class TestRender(CiTestCase):
28 write_file(instance_data, '{}')29 write_file(instance_data, '{}')
29 args = self.args(30 args = self.args(
30 user_data=absent_file, instance_data=instance_data, debug=False)31 user_data=absent_file, instance_data=instance_data, debug=False)
31 with mock.patch('sys.stderr', new_callable=StringIO):32 self.assertEqual(1, render.handle_args('anyname', args))
32 self.assertEqual(1, render.handle_args('anyname', args))
33 self.assertIn(33 self.assertIn(
34 'Missing user-data file: %s' % absent_file,34 'Missing user-data file: %s' % absent_file,
35 self.logs.getvalue())35 self.logs.getvalue())
@@ -40,8 +40,7 @@ class TestRender(CiTestCase):
40 absent_file = self.tmp_path('instance-data', dir=self.tmp)40 absent_file = self.tmp_path('instance-data', dir=self.tmp)
41 args = self.args(41 args = self.args(
42 user_data=user_data, instance_data=absent_file, debug=False)42 user_data=user_data, instance_data=absent_file, debug=False)
43 with mock.patch('sys.stderr', new_callable=StringIO):43 self.assertEqual(1, render.handle_args('anyname', args))
44 self.assertEqual(1, render.handle_args('anyname', args))
45 self.assertIn(44 self.assertIn(
46 'Missing instance-data.json file: %s' % absent_file,45 'Missing instance-data.json file: %s' % absent_file,
47 self.logs.getvalue())46 self.logs.getvalue())
@@ -52,12 +51,11 @@ class TestRender(CiTestCase):
52 run_dir = self.tmp_path('run_dir', dir=self.tmp)51 run_dir = self.tmp_path('run_dir', dir=self.tmp)
53 ensure_dir(run_dir)52 ensure_dir(run_dir)
54 paths = Paths({'run_dir': run_dir})53 paths = Paths({'run_dir': run_dir})
55 self.add_patch('cloudinit.cmd.devel.render.read_cfg_paths', 'm_paths')54 self.add_patch('cloudinit.stages.read_cfg_paths', 'm_paths')
56 self.m_paths.return_value = paths55 self.m_paths.return_value = paths
57 args = self.args(56 args = self.args(
58 user_data=user_data, instance_data=None, debug=False)57 user_data=user_data, instance_data=None, debug=False)
59 with mock.patch('sys.stderr', new_callable=StringIO):58 self.assertEqual(1, render.handle_args('anyname', args))
60 self.assertEqual(1, render.handle_args('anyname', args))
61 json_file = os.path.join(run_dir, INSTANCE_JSON_FILE)59 json_file = os.path.join(run_dir, INSTANCE_JSON_FILE)
62 self.assertIn(60 self.assertIn(
63 'Missing instance-data.json file: %s' % json_file,61 'Missing instance-data.json file: %s' % json_file,
@@ -69,14 +67,13 @@ class TestRender(CiTestCase):
69 run_dir = self.tmp_path('run_dir', dir=self.tmp)67 run_dir = self.tmp_path('run_dir', dir=self.tmp)
70 ensure_dir(run_dir)68 ensure_dir(run_dir)
71 paths = Paths({'run_dir': run_dir})69 paths = Paths({'run_dir': run_dir})
72 self.add_patch('cloudinit.cmd.devel.render.read_cfg_paths', 'm_paths')70 self.add_patch('cloudinit.stages.read_cfg_paths', 'm_paths')
73 self.m_paths.return_value = paths71 self.m_paths.return_value = paths
74 args = self.args(72 args = self.args(
75 user_data=user_data, instance_data=None, debug=False)73 user_data=user_data, instance_data=None, debug=False)
76 with mock.patch('sys.stderr', new_callable=StringIO):74 with mock.patch('os.getuid') as m_getuid:
77 with mock.patch('os.getuid') as m_getuid:75 m_getuid.return_value = 0
78 m_getuid.return_value = 076 self.assertEqual(1, render.handle_args('anyname', args))
79 self.assertEqual(1, render.handle_args('anyname', args))
80 json_file = os.path.join(run_dir, INSTANCE_JSON_FILE)77 json_file = os.path.join(run_dir, INSTANCE_JSON_FILE)
81 json_sensitive = os.path.join(run_dir, INSTANCE_JSON_SENSITIVE_FILE)78 json_sensitive = os.path.join(run_dir, INSTANCE_JSON_SENSITIVE_FILE)
82 self.assertIn(79 self.assertIn(
@@ -95,15 +92,14 @@ class TestRender(CiTestCase):
95 json_sensitive = os.path.join(run_dir, INSTANCE_JSON_SENSITIVE_FILE)92 json_sensitive = os.path.join(run_dir, INSTANCE_JSON_SENSITIVE_FILE)
96 write_file(json_sensitive, '{"my-var": "jinja worked"}')93 write_file(json_sensitive, '{"my-var": "jinja worked"}')
97 paths = Paths({'run_dir': run_dir})94 paths = Paths({'run_dir': run_dir})
98 self.add_patch('cloudinit.cmd.devel.render.read_cfg_paths', 'm_paths')95 self.add_patch('cloudinit.stages.read_cfg_paths', 'm_paths')
99 self.m_paths.return_value = paths96 self.m_paths.return_value = paths
100 args = self.args(97 args = self.args(
101 user_data=user_data, instance_data=None, debug=False)98 user_data=user_data, instance_data=None, debug=False)
102 with mock.patch('sys.stderr', new_callable=StringIO):99 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
103 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:100 with mock.patch('os.getuid') as m_getuid:
104 with mock.patch('os.getuid') as m_getuid:101 m_getuid.return_value = 0
105 m_getuid.return_value = 0102 self.assertEqual(0, render.handle_args('anyname', args))
106 self.assertEqual(0, render.handle_args('anyname', args))
107 self.assertIn('rendering: jinja worked', m_stdout.getvalue())103 self.assertIn('rendering: jinja worked', m_stdout.getvalue())
108104
109 @skipUnlessJinja()105 @skipUnlessJinja()
@@ -115,13 +111,10 @@ class TestRender(CiTestCase):
115 write_file(instance_data, '{"my-var": "jinja worked"}')111 write_file(instance_data, '{"my-var": "jinja worked"}')
116 args = self.args(112 args = self.args(
117 user_data=user_data, instance_data=instance_data, debug=True)113 user_data=user_data, instance_data=instance_data, debug=True)
118 with mock.patch('sys.stderr', new_callable=StringIO) as m_console_err:114 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
119 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:115 self.assertEqual(0, render.handle_args('anyname', args))
120 self.assertEqual(0, render.handle_args('anyname', args))
121 self.assertIn(116 self.assertIn(
122 'DEBUG: Converted jinja variables\n{', self.logs.getvalue())117 'DEBUG: Converted jinja variables\n{', self.logs.getvalue())
123 self.assertIn(
124 'DEBUG: Converted jinja variables\n{', m_console_err.getvalue())
125 self.assertEqual('rendering: jinja worked', m_stdout.getvalue())118 self.assertEqual('rendering: jinja worked', m_stdout.getvalue())
126119
127 @skipUnlessJinja()120 @skipUnlessJinja()
@@ -133,12 +126,16 @@ class TestRender(CiTestCase):
133 write_file(instance_data, '{"my-var": "jinja worked"}')126 write_file(instance_data, '{"my-var": "jinja worked"}')
134 args = self.args(127 args = self.args(
135 user_data=user_data, instance_data=instance_data, debug=True)128 user_data=user_data, instance_data=instance_data, debug=True)
136 with mock.patch('sys.stderr', new_callable=StringIO):129 self.assertEqual(1, render.handle_args('anyname', args))
137 self.assertEqual(1, render.handle_args('anyname', args))130 if PY26:
138 self.assertIn(131 msg = (
139 'WARNING: Ignoring jinja template for %s: Undefined jinja'132 'WARNING: Ignoring jinja template for %s: \'var\' is'
140 ' variable: "my-var". Jinja tried subtraction. Perhaps you meant'133 ' undefined' % user_data)
141 ' "my_var"?' % user_data,134 else:
142 self.logs.getvalue())135 msg = (
136 'WARNING: Ignoring jinja template for %s: Undefined jinja'
137 ' variable: "my-var". Jinja tried subtraction. Perhaps you'
138 ' meant "my_var"?' % user_data)
139 self.assertIn(msg, self.logs.getvalue())
143140
144# vi: ts=4 expandtab141# vi: ts=4 expandtab
diff --git a/cloudinit/cmd/query.py b/cloudinit/cmd/query.py
index 1d888b9..07d9d5f 100644
--- a/cloudinit/cmd/query.py
+++ b/cloudinit/cmd/query.py
@@ -10,10 +10,10 @@ import sys
1010
11from cloudinit.handlers.jinja_template import (11from cloudinit.handlers.jinja_template import (
12 convert_jinja_instance_data, render_jinja_payload)12 convert_jinja_instance_data, render_jinja_payload)
13from cloudinit.cmd.devel import addLogHandlerCLI, read_cfg_paths
14from cloudinit import log13from cloudinit import log
15from cloudinit.sources import (14from cloudinit.sources import (
16 INSTANCE_JSON_FILE, INSTANCE_JSON_SENSITIVE_FILE, REDACT_SENSITIVE_VALUE)15 INSTANCE_JSON_FILE, INSTANCE_JSON_SENSITIVE_FILE, REDACT_SENSITIVE_VALUE)
16from cloudinit import stages
17from cloudinit import util17from cloudinit import util
1818
19NAME = 'query'19NAME = 'query'
@@ -69,7 +69,7 @@ def get_parser(parser=None):
69def handle_args(name, args):69def handle_args(name, args):
70 """Handle calls to 'cloud-init query' as a subcommand."""70 """Handle calls to 'cloud-init query' as a subcommand."""
71 paths = None71 paths = None
72 addLogHandlerCLI(LOG, log.DEBUG if args.debug else log.WARNING)72 log.addLogHandlerCLI(LOG, log.DEBUG if args.debug else log.WARNING)
73 if not any([args.list_keys, args.varname, args.format, args.dump_all]):73 if not any([args.list_keys, args.varname, args.format, args.dump_all]):
74 LOG.error(74 LOG.error(
75 'Expected one of the options: --all, --format,'75 'Expected one of the options: --all, --format,'
@@ -79,7 +79,7 @@ def handle_args(name, args):
7979
80 uid = os.getuid()80 uid = os.getuid()
81 if not all([args.instance_data, args.user_data, args.vendor_data]):81 if not all([args.instance_data, args.user_data, args.vendor_data]):
82 paths = read_cfg_paths()82 paths = stages.read_cfg_paths()
83 if args.instance_data:83 if args.instance_data:
84 instance_data_fn = args.instance_data84 instance_data_fn = args.instance_data
85 else:85 else:
diff --git a/cloudinit/cmd/tests/test_clean.py b/cloudinit/cmd/tests/test_clean.py
index 5a3ec3b..266db32 100644
--- a/cloudinit/cmd/tests/test_clean.py
+++ b/cloudinit/cmd/tests/test_clean.py
@@ -169,7 +169,7 @@ class TestClean(CiTestCase):
169 'sys.argv': {'new': ['clean', '--logs']}},169 'sys.argv': {'new': ['clean', '--logs']}},
170 clean.main)170 clean.main)
171171
172 self.assertEqual(0, context_manager.exception.code)172 self.assertEqual('0', str(context_manager.exception))
173 self.assertFalse(173 self.assertFalse(
174 os.path.exists(self.log1), 'Unexpected log {0}'.format(self.log1))174 os.path.exists(self.log1), 'Unexpected log {0}'.format(self.log1))
175175
diff --git a/cloudinit/cmd/tests/test_cloud_id.py b/cloudinit/cmd/tests/test_cloud_id.py
index 7373817..3998272 100644
--- a/cloudinit/cmd/tests/test_cloud_id.py
+++ b/cloudinit/cmd/tests/test_cloud_id.py
@@ -49,7 +49,7 @@ class TestCloudId(CiTestCase):
49 with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:49 with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:
50 with self.assertRaises(SystemExit) as context_manager:50 with self.assertRaises(SystemExit) as context_manager:
51 cloud_id.main()51 cloud_id.main()
52 self.assertEqual(1, context_manager.exception.code)52 self.assertEqual('1', str(context_manager.exception))
53 self.assertIn(53 self.assertIn(
54 "ERROR: File not found '%s'" % self.instance_data,54 "ERROR: File not found '%s'" % self.instance_data,
55 m_stderr.getvalue())55 m_stderr.getvalue())
@@ -62,7 +62,7 @@ class TestCloudId(CiTestCase):
62 with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:62 with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:
63 with self.assertRaises(SystemExit) as context_manager:63 with self.assertRaises(SystemExit) as context_manager:
64 cloud_id.main()64 cloud_id.main()
65 self.assertEqual(1, context_manager.exception.code)65 self.assertEqual('1', str(context_manager.exception))
66 self.assertIn(66 self.assertIn(
67 "ERROR: File '%s' is not valid json." % self.instance_data,67 "ERROR: File '%s' is not valid json." % self.instance_data,
68 m_stderr.getvalue())68 m_stderr.getvalue())
@@ -77,7 +77,7 @@ class TestCloudId(CiTestCase):
77 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:77 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
78 with self.assertRaises(SystemExit) as context_manager:78 with self.assertRaises(SystemExit) as context_manager:
79 cloud_id.main()79 cloud_id.main()
80 self.assertEqual(0, context_manager.exception.code)80 self.assertEqual('0', str(context_manager.exception))
81 self.assertEqual("mycloud\n", m_stdout.getvalue())81 self.assertEqual("mycloud\n", m_stdout.getvalue())
8282
83 def test_cloud_id_long_name_from_instance_data(self):83 def test_cloud_id_long_name_from_instance_data(self):
@@ -90,7 +90,7 @@ class TestCloudId(CiTestCase):
90 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:90 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
91 with self.assertRaises(SystemExit) as context_manager:91 with self.assertRaises(SystemExit) as context_manager:
92 cloud_id.main()92 cloud_id.main()
93 self.assertEqual(0, context_manager.exception.code)93 self.assertEqual('0', str(context_manager.exception))
94 self.assertEqual("mycloud\tsomereg\n", m_stdout.getvalue())94 self.assertEqual("mycloud\tsomereg\n", m_stdout.getvalue())
9595
96 def test_cloud_id_lookup_from_instance_data_region(self):96 def test_cloud_id_lookup_from_instance_data_region(self):
@@ -104,7 +104,7 @@ class TestCloudId(CiTestCase):
104 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:104 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
105 with self.assertRaises(SystemExit) as context_manager:105 with self.assertRaises(SystemExit) as context_manager:
106 cloud_id.main()106 cloud_id.main()
107 self.assertEqual(0, context_manager.exception.code)107 self.assertEqual('0', str(context_manager.exception))
108 self.assertEqual("aws-china\tcn-north-1\n", m_stdout.getvalue())108 self.assertEqual("aws-china\tcn-north-1\n", m_stdout.getvalue())
109109
110 def test_cloud_id_lookup_json_instance_data_adds_cloud_id_to_json(self):110 def test_cloud_id_lookup_json_instance_data_adds_cloud_id_to_json(self):
@@ -121,7 +121,7 @@ class TestCloudId(CiTestCase):
121 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:121 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
122 with self.assertRaises(SystemExit) as context_manager:122 with self.assertRaises(SystemExit) as context_manager:
123 cloud_id.main()123 cloud_id.main()
124 self.assertEqual(0, context_manager.exception.code)124 self.assertEqual('0', str(context_manager.exception))
125 self.assertEqual(expected + '\n', m_stdout.getvalue())125 self.assertEqual(expected + '\n', m_stdout.getvalue())
126126
127# vi: ts=4 expandtab127# vi: ts=4 expandtab
diff --git a/cloudinit/cmd/tests/test_query.py b/cloudinit/cmd/tests/test_query.py
index 28738b1..3b32bd3 100644
--- a/cloudinit/cmd/tests/test_query.py
+++ b/cloudinit/cmd/tests/test_query.py
@@ -27,21 +27,20 @@ class TestQuery(CiTestCase):
27 super(TestQuery, self).setUp()27 super(TestQuery, self).setUp()
28 self.tmp = self.tmp_dir()28 self.tmp = self.tmp_dir()
29 self.instance_data = self.tmp_path('instance-data', dir=self.tmp)29 self.instance_data = self.tmp_path('instance-data', dir=self.tmp)
30 self.add_patch('cloudinit.log.addLogHandlerCLI', 'm_logcli')
3031
31 def test_handle_args_error_on_missing_param(self):32 def test_handle_args_error_on_missing_param(self):
32 """Error when missing required parameters and print usage."""33 """Error when missing required parameters and print usage."""
33 args = self.args(34 args = self.args(
34 debug=False, dump_all=False, format=None, instance_data=None,35 debug=False, dump_all=False, format=None, instance_data=None,
35 list_keys=False, user_data=None, vendor_data=None, varname=None)36 list_keys=False, user_data=None, vendor_data=None, varname=None)
36 with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:37 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
37 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:38 self.assertEqual(1, query.handle_args('anyname', args))
38 self.assertEqual(1, query.handle_args('anyname', args))
39 expected_error = (39 expected_error = (
40 'ERROR: Expected one of the options: --all, --format, --list-keys'40 'ERROR: Expected one of the options: --all, --format, --list-keys'
41 ' or varname\n')41 ' or varname\n')
42 self.assertIn(expected_error, self.logs.getvalue())42 self.assertIn(expected_error, self.logs.getvalue())
43 self.assertIn('usage: query', m_stdout.getvalue())43 self.assertIn('usage: query', m_stdout.getvalue())
44 self.assertIn(expected_error, m_stderr.getvalue())
4544
46 def test_handle_args_error_on_missing_instance_data(self):45 def test_handle_args_error_on_missing_instance_data(self):
47 """When instance_data file path does not exist, log an error."""46 """When instance_data file path does not exist, log an error."""
@@ -49,14 +48,10 @@ class TestQuery(CiTestCase):
49 args = self.args(48 args = self.args(
50 debug=False, dump_all=True, format=None, instance_data=absent_fn,49 debug=False, dump_all=True, format=None, instance_data=absent_fn,
51 list_keys=False, user_data='ud', vendor_data='vd', varname=None)50 list_keys=False, user_data='ud', vendor_data='vd', varname=None)
52 with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:51 self.assertEqual(1, query.handle_args('anyname', args))
53 self.assertEqual(1, query.handle_args('anyname', args))
54 self.assertIn(52 self.assertIn(
55 'ERROR: Missing instance-data file: %s' % absent_fn,53 'ERROR: Missing instance-data file: %s' % absent_fn,
56 self.logs.getvalue())54 self.logs.getvalue())
57 self.assertIn(
58 'ERROR: Missing instance-data file: %s' % absent_fn,
59 m_stderr.getvalue())
6055
61 def test_handle_args_error_when_no_read_permission_instance_data(self):56 def test_handle_args_error_when_no_read_permission_instance_data(self):
62 """When instance_data file is unreadable, log an error."""57 """When instance_data file is unreadable, log an error."""
@@ -65,16 +60,12 @@ class TestQuery(CiTestCase):
65 args = self.args(60 args = self.args(
66 debug=False, dump_all=True, format=None, instance_data=noread_fn,61 debug=False, dump_all=True, format=None, instance_data=noread_fn,
67 list_keys=False, user_data='ud', vendor_data='vd', varname=None)62 list_keys=False, user_data='ud', vendor_data='vd', varname=None)
68 with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:63 with mock.patch('cloudinit.cmd.query.util.load_file') as m_load:
69 with mock.patch('cloudinit.cmd.query.util.load_file') as m_load:64 m_load.side_effect = OSError(errno.EACCES, 'Not allowed')
70 m_load.side_effect = OSError(errno.EACCES, 'Not allowed')65 self.assertEqual(1, query.handle_args('anyname', args))
71 self.assertEqual(1, query.handle_args('anyname', args))
72 self.assertIn(66 self.assertIn(
73 "ERROR: No read permission on '%s'. Try sudo" % noread_fn,67 "ERROR: No read permission on '%s'. Try sudo" % noread_fn,
74 self.logs.getvalue())68 self.logs.getvalue())
75 self.assertIn(
76 "ERROR: No read permission on '%s'. Try sudo" % noread_fn,
77 m_stderr.getvalue())
7869
79 def test_handle_args_defaults_instance_data(self):70 def test_handle_args_defaults_instance_data(self):
80 """When no instance_data argument, default to configured run_dir."""71 """When no instance_data argument, default to configured run_dir."""
@@ -84,17 +75,13 @@ class TestQuery(CiTestCase):
84 run_dir = self.tmp_path('run_dir', dir=self.tmp)75 run_dir = self.tmp_path('run_dir', dir=self.tmp)
85 ensure_dir(run_dir)76 ensure_dir(run_dir)
86 paths = Paths({'run_dir': run_dir})77 paths = Paths({'run_dir': run_dir})
87 self.add_patch('cloudinit.cmd.query.read_cfg_paths', 'm_paths')78 self.add_patch('cloudinit.stages.read_cfg_paths', 'm_paths')
88 self.m_paths.return_value = paths79 self.m_paths.return_value = paths
89 with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:80 self.assertEqual(1, query.handle_args('anyname', args))
90 self.assertEqual(1, query.handle_args('anyname', args))
91 json_file = os.path.join(run_dir, INSTANCE_JSON_FILE)81 json_file = os.path.join(run_dir, INSTANCE_JSON_FILE)
92 self.assertIn(82 self.assertIn(
93 'ERROR: Missing instance-data file: %s' % json_file,83 'ERROR: Missing instance-data file: %s' % json_file,
94 self.logs.getvalue())84 self.logs.getvalue())
95 self.assertIn(
96 'ERROR: Missing instance-data file: %s' % json_file,
97 m_stderr.getvalue())
9885
99 def test_handle_args_root_fallsback_to_instance_data(self):86 def test_handle_args_root_fallsback_to_instance_data(self):
100 """When no instance_data argument, root falls back to redacted json."""87 """When no instance_data argument, root falls back to redacted json."""
@@ -104,18 +91,17 @@ class TestQuery(CiTestCase):
104 run_dir = self.tmp_path('run_dir', dir=self.tmp)91 run_dir = self.tmp_path('run_dir', dir=self.tmp)
105 ensure_dir(run_dir)92 ensure_dir(run_dir)
106 paths = Paths({'run_dir': run_dir})93 paths = Paths({'run_dir': run_dir})
107 self.add_patch('cloudinit.cmd.query.read_cfg_paths', 'm_paths')94 self.add_patch('cloudinit.stages.read_cfg_paths', 'm_paths')
108 self.m_paths.return_value = paths95 self.m_paths.return_value = paths
109 with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:96 with mock.patch('os.getuid') as m_getuid:
110 with mock.patch('os.getuid') as m_getuid:97 m_getuid.return_value = 0
111 m_getuid.return_value = 098 self.assertEqual(1, query.handle_args('anyname', args))
112 self.assertEqual(1, query.handle_args('anyname', args))
113 json_file = os.path.join(run_dir, INSTANCE_JSON_FILE)99 json_file = os.path.join(run_dir, INSTANCE_JSON_FILE)
114 sensitive_file = os.path.join(run_dir, INSTANCE_JSON_SENSITIVE_FILE)100 sensitive_file = os.path.join(run_dir, INSTANCE_JSON_SENSITIVE_FILE)
115 self.assertIn(101 self.assertIn(
116 'WARNING: Missing root-readable %s. Using redacted %s instead.' % (102 'WARNING: Missing root-readable %s. Using redacted %s instead.' % (
117 sensitive_file, json_file),103 sensitive_file, json_file),
118 m_stderr.getvalue())104 self.logs.getvalue())
119105
120 def test_handle_args_root_uses_instance_sensitive_data(self):106 def test_handle_args_root_uses_instance_sensitive_data(self):
121 """When no instance_data argument, root uses semsitive json."""107 """When no instance_data argument, root uses semsitive json."""
@@ -128,7 +114,7 @@ class TestQuery(CiTestCase):
128 write_file(sensitive_file, '{"my-var": "it worked"}')114 write_file(sensitive_file, '{"my-var": "it worked"}')
129 ensure_dir(run_dir)115 ensure_dir(run_dir)
130 paths = Paths({'run_dir': run_dir})116 paths = Paths({'run_dir': run_dir})
131 self.add_patch('cloudinit.cmd.query.read_cfg_paths', 'm_paths')117 self.add_patch('cloudinit.stages.read_cfg_paths', 'm_paths')
132 self.m_paths.return_value = paths118 self.m_paths.return_value = paths
133 args = self.args(119 args = self.args(
134 debug=False, dump_all=True, format=None, instance_data=None,120 debug=False, dump_all=True, format=None, instance_data=None,
@@ -150,7 +136,9 @@ class TestQuery(CiTestCase):
150 instance_data=self.instance_data, list_keys=False,136 instance_data=self.instance_data, list_keys=False,
151 user_data='ud', vendor_data='vd', varname=None)137 user_data='ud', vendor_data='vd', varname=None)
152 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:138 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
153 self.assertEqual(0, query.handle_args('anyname', args))139 with mock.patch('os.getuid') as m_getuid:
140 m_getuid.return_value = 100
141 self.assertEqual(0, query.handle_args('anyname', args))
154 self.assertEqual(142 self.assertEqual(
155 '{\n "my_var": "it worked",\n "userdata": "<%s> file:ud",\n'143 '{\n "my_var": "it worked",\n "userdata": "<%s> file:ud",\n'
156 ' "vendordata": "<%s> file:vd"\n}\n' % (144 ' "vendordata": "<%s> file:vd"\n}\n' % (
@@ -177,7 +165,9 @@ class TestQuery(CiTestCase):
177 instance_data=self.instance_data, user_data='ud', vendor_data='vd',165 instance_data=self.instance_data, user_data='ud', vendor_data='vd',
178 list_keys=False, varname='v1.key_2')166 list_keys=False, varname='v1.key_2')
179 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:167 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
180 self.assertEqual(0, query.handle_args('anyname', args))168 with mock.patch('os.getuid') as m_getuid:
169 m_getuid.return_value = 100
170 self.assertEqual(0, query.handle_args('anyname', args))
181 self.assertEqual('value-2\n', m_stdout.getvalue())171 self.assertEqual('value-2\n', m_stdout.getvalue())
182172
183 def test_handle_args_returns_standardized_vars_to_top_level_aliases(self):173 def test_handle_args_returns_standardized_vars_to_top_level_aliases(self):
@@ -206,7 +196,9 @@ class TestQuery(CiTestCase):
206 instance_data=self.instance_data, user_data='ud', vendor_data='vd',196 instance_data=self.instance_data, user_data='ud', vendor_data='vd',
207 list_keys=False, varname=None)197 list_keys=False, varname=None)
208 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:198 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
209 self.assertEqual(0, query.handle_args('anyname', args))199 with mock.patch('os.getuid') as m_getuid:
200 m_getuid.return_value = 100
201 self.assertEqual(0, query.handle_args('anyname', args))
210 self.assertEqual(expected, m_stdout.getvalue())202 self.assertEqual(expected, m_stdout.getvalue())
211203
212 def test_handle_args_list_keys_sorts_top_level_keys_when_no_varname(self):204 def test_handle_args_list_keys_sorts_top_level_keys_when_no_varname(self):
@@ -221,7 +213,9 @@ class TestQuery(CiTestCase):
221 instance_data=self.instance_data, list_keys=True, user_data='ud',213 instance_data=self.instance_data, list_keys=True, user_data='ud',
222 vendor_data='vd', varname=None)214 vendor_data='vd', varname=None)
223 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:215 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
224 self.assertEqual(0, query.handle_args('anyname', args))216 with mock.patch('os.getuid') as m_getuid:
217 m_getuid.return_value = 100
218 self.assertEqual(0, query.handle_args('anyname', args))
225 self.assertEqual(expected, m_stdout.getvalue())219 self.assertEqual(expected, m_stdout.getvalue())
226220
227 def test_handle_args_list_keys_sorts_nested_keys_when_varname(self):221 def test_handle_args_list_keys_sorts_nested_keys_when_varname(self):
@@ -236,7 +230,9 @@ class TestQuery(CiTestCase):
236 instance_data=self.instance_data, list_keys=True,230 instance_data=self.instance_data, list_keys=True,
237 user_data='ud', vendor_data='vd', varname='v1')231 user_data='ud', vendor_data='vd', varname='v1')
238 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:232 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
239 self.assertEqual(0, query.handle_args('anyname', args))233 with mock.patch('os.getuid') as m_getuid:
234 m_getuid.return_value = 100
235 self.assertEqual(0, query.handle_args('anyname', args))
240 self.assertEqual(expected, m_stdout.getvalue())236 self.assertEqual(expected, m_stdout.getvalue())
241237
242 def test_handle_args_list_keys_errors_when_varname_is_not_a_dict(self):238 def test_handle_args_list_keys_errors_when_varname_is_not_a_dict(self):
@@ -250,10 +246,11 @@ class TestQuery(CiTestCase):
250 debug=False, dump_all=False, format=None,246 debug=False, dump_all=False, format=None,
251 instance_data=self.instance_data, list_keys=True, user_data='ud',247 instance_data=self.instance_data, list_keys=True, user_data='ud',
252 vendor_data='vd', varname='top')248 vendor_data='vd', varname='top')
253 with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:249 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
254 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:250 with mock.patch('os.getuid') as m_getuid:
251 m_getuid.return_value = 100
255 self.assertEqual(1, query.handle_args('anyname', args))252 self.assertEqual(1, query.handle_args('anyname', args))
256 self.assertEqual('', m_stdout.getvalue())253 self.assertEqual('', m_stdout.getvalue())
257 self.assertIn(expected_error, m_stderr.getvalue())254 self.assertIn(expected_error, self.logs.getvalue())
258255
259# vi: ts=4 expandtab256# vi: ts=4 expandtab
diff --git a/cloudinit/cmd/tests/test_status.py b/cloudinit/cmd/tests/test_status.py
index aded858..9f742df 100644
--- a/cloudinit/cmd/tests/test_status.py
+++ b/cloudinit/cmd/tests/test_status.py
@@ -386,7 +386,7 @@ class TestStatus(CiTestCase):
386 '_is_cloudinit_disabled': (False, ''),386 '_is_cloudinit_disabled': (False, ''),
387 'Init': {'side_effect': self.init_class}},387 'Init': {'side_effect': self.init_class}},
388 status.main)388 status.main)
389 self.assertEqual(0, context_manager.exception.code)389 self.assertEqual('0', str(context_manager.exception))
390 self.assertEqual('status: running\n', m_stdout.getvalue())390 self.assertEqual('status: running\n', m_stdout.getvalue())
391391
392# vi: ts=4 expandtab syntax=python392# vi: ts=4 expandtab syntax=python
diff --git a/cloudinit/helpers.py b/cloudinit/helpers.py
index dcd2645..22ecacd 100644
--- a/cloudinit/helpers.py
+++ b/cloudinit/helpers.py
@@ -457,4 +457,11 @@ class DefaultingConfigParser(RawConfigParser):
457def identity(object):457def identity(object):
458 return object458 return object
459459
460
461def implement_unicode_from_str(object):
462 """Add __unicode__ method to object from __str__ behavior."""
463 object.__unicode__ = object.__str__
464 object.__str__ = lambda x: x.__unicode().encode('utf-8')
465 return object
466
460# vi: ts=4 expandtab467# vi: ts=4 expandtab
diff --git a/cloudinit/log.py b/cloudinit/log.py
index 5ae312b..f63190a 100644
--- a/cloudinit/log.py
+++ b/cloudinit/log.py
@@ -126,6 +126,13 @@ def getLogger(name='cloudinit'):
126 return logging.getLogger(name)126 return logging.getLogger(name)
127127
128128
129def addLogHandlerCLI(logger, log_level):
130 """Add a commandline logging handler to emit messages to stderr."""
131 formatter = logging.Formatter('%(levelname)s: %(message)s')
132 setupBasicLogging(log_level, formatter=formatter)
133 return logger
134
135
129# Fixes this annoyance...136# Fixes this annoyance...
130# No handlers could be found for logger XXX annoying output...137# No handlers could be found for logger XXX annoying output...
131try:138try:
diff --git a/cloudinit/sources/helpers/netlink.py b/cloudinit/sources/helpers/netlink.py
index d377ae3..89b4e6e 100644
--- a/cloudinit/sources/helpers/netlink.py
+++ b/cloudinit/sources/helpers/netlink.py
@@ -97,6 +97,8 @@ def get_netlink_msg_header(data):
97 assert (data is not None), ("data is none")97 assert (data is not None), ("data is none")
98 assert (len(data) >= NLMSGHDR_SIZE), (98 assert (len(data) >= NLMSGHDR_SIZE), (
99 "data is smaller than netlink message header")99 "data is smaller than netlink message header")
100 if util.PY26:
101 data = str(data)
100 msg_len, msg_type, flags, seq, pid = struct.unpack(NLMSGHDR_FMT,102 msg_len, msg_type, flags, seq, pid = struct.unpack(NLMSGHDR_FMT,
101 data[:MSG_TYPE_OFFSET])103 data[:MSG_TYPE_OFFSET])
102 LOG.debug("Got netlink msg of type %d", msg_type)104 LOG.debug("Got netlink msg of type %d", msg_type)
@@ -140,6 +142,8 @@ def unpack_rta_attr(data, offset):
140 "rta offset is less than expected length")142 "rta offset is less than expected length")
141 length = rta_type = 0143 length = rta_type = 0
142 attr_data = None144 attr_data = None
145 if util.PY26:
146 data = str(data)
143 try:147 try:
144 length = struct.unpack_from("H", data, offset=offset)[0]148 length = struct.unpack_from("H", data, offset=offset)[0]
145 rta_type = struct.unpack_from("H", data, offset=offset+2)[0]149 rta_type = struct.unpack_from("H", data, offset=offset+2)[0]
diff --git a/cloudinit/stages.py b/cloudinit/stages.py
index 8a06412..badc883 100644
--- a/cloudinit/stages.py
+++ b/cloudinit/stages.py
@@ -889,6 +889,13 @@ def fetch_base_config():
889 ], reverse=True)889 ], reverse=True)
890890
891891
892def read_cfg_paths():
893 """Return a Paths object based on the system configuration on disk."""
894 init = Init(ds_deps=[])
895 init.read_cfg()
896 return init.paths
897
898
892def _pkl_store(obj, fname):899def _pkl_store(obj, fname):
893 try:900 try:
894 pk_contents = pickle.dumps(obj)901 pk_contents = pickle.dumps(obj)
diff --git a/cloudinit/templater.py b/cloudinit/templater.py
index b668674..85a5b0f 100644
--- a/cloudinit/templater.py
+++ b/cloudinit/templater.py
@@ -13,6 +13,9 @@
13import collections13import collections
14import re14import re
1515
16from cloudinit import log as logging
17from cloudinit import type_utils as tu
18from cloudinit import util
1619
17try:20try:
18 from Cheetah.Template import Template as CTemplate21 from Cheetah.Template import Template as CTemplate
@@ -21,19 +24,24 @@ except (ImportError, AttributeError):
21 CHEETAH_AVAILABLE = False24 CHEETAH_AVAILABLE = False
2225
23try:26try:
24 from jinja2.runtime import implements_to_string
25 from jinja2 import Template as JTemplate27 from jinja2 import Template as JTemplate
26 from jinja2 import DebugUndefined as JUndefined28 from jinja2 import DebugUndefined as JUndefined
27 JINJA_AVAILABLE = True29 JINJA_AVAILABLE = True
28except (ImportError, AttributeError):30except (ImportError, AttributeError):
29 from cloudinit.helpers import identity
30 implements_to_string = identity
31 JINJA_AVAILABLE = False31 JINJA_AVAILABLE = False
32 JUndefined = object32 JUndefined = object
3333
34from cloudinit import log as logging34if JINJA_AVAILABLE:
35from cloudinit import type_utils as tu35 try:
36from cloudinit import util36 # centos 6 jinja2 (2.2.1) works other than missing implements_to_string
37 from jinja2.runtime import implements_to_string
38 except ImportError:
39 if not hasattr(JUndefined, '__unicode__'):
40 from cloudinit.helpers import identity
41 implements_to_string = identity
42 else:
43 from cloudinit.helpers import implement_unicode_from_str
44 implements_to_string = implement_unicode_from_str
3745
3846
39LOG = logging.getLogger(__name__)47LOG = logging.getLogger(__name__)
@@ -47,7 +55,11 @@ class UndefinedJinjaVariable(JUndefined):
47 """Class used to represent any undefined jinja template varible."""55 """Class used to represent any undefined jinja template varible."""
4856
49 def __str__(self):57 def __str__(self):
50 return u'%s%s' % (MISSING_JINJA_PREFIX, self._undefined_name)58 if hasattr(self, '_undefined_name'):
59 hint = getattr(self, '_undefined_name')
60 else:
61 hint = getattr(self, '_undefined_hint')
62 return u'%s%s' % (MISSING_JINJA_PREFIX, hint)
5163
52 def __sub__(self, other):64 def __sub__(self, other):
53 other = str(other).replace(MISSING_JINJA_PREFIX, '')65 other = str(other).replace(MISSING_JINJA_PREFIX, '')
diff --git a/tests/unittests/test_builtin_handlers.py b/tests/unittests/test_builtin_handlers.py
index b92ffc7..b95de01 100644
--- a/tests/unittests/test_builtin_handlers.py
+++ b/tests/unittests/test_builtin_handlers.py
@@ -197,7 +197,7 @@ class TestJinjaTemplatePartHandler(CiTestCase):
197 script_file = os.path.join(script_handler.script_dir, 'part01')197 script_file = os.path.join(script_handler.script_dir, 'part01')
198 self.assertEqual(198 self.assertEqual(
199 'Cannot render jinja template vars. Instance data not yet present'199 'Cannot render jinja template vars. Instance data not yet present'
200 ' at {}/instance-data.json'.format(200 ' at {0}/instance-data.json'.format(
201 self.run_dir), str(context_manager.exception))201 self.run_dir), str(context_manager.exception))
202 self.assertFalse(202 self.assertFalse(
203 os.path.exists(script_file),203 os.path.exists(script_file),
@@ -246,7 +246,7 @@ class TestJinjaTemplatePartHandler(CiTestCase):
246 frequency='freq', headers='headers')246 frequency='freq', headers='headers')
247 script_file = os.path.join(script_handler.script_dir, 'part01')247 script_file = os.path.join(script_handler.script_dir, 'part01')
248 self.assertNotIn(248 self.assertNotIn(
249 'Instance data not yet present at {}/instance-data.json'.format(249 'Instance data not yet present at {0}/instance-data.json'.format(
250 self.run_dir),250 self.run_dir),
251 self.logs.getvalue())251 self.logs.getvalue())
252 self.assertEqual(252 self.assertEqual(
diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py
index 1bad07f..c8e5968 100644
--- a/tests/unittests/test_handler/test_schema.py
+++ b/tests/unittests/test_handler/test_schema.py
@@ -350,7 +350,7 @@ class MainTest(CiTestCase):
350 m_stderr:350 m_stderr:
351 with self.assertRaises(SystemExit) as context_manager:351 with self.assertRaises(SystemExit) as context_manager:
352 main()352 main()
353 self.assertEqual(1, context_manager.exception.code)353 self.assertEqual('1', str(context_manager.exception))
354 self.assertEqual(354 self.assertEqual(
355 'Expected either --config-file argument or --doc\n',355 'Expected either --config-file argument or --doc\n',
356 m_stderr.getvalue())356 m_stderr.getvalue())
@@ -364,7 +364,7 @@ class MainTest(CiTestCase):
364 m_stderr:364 m_stderr:
365 with self.assertRaises(SystemExit) as context_manager:365 with self.assertRaises(SystemExit) as context_manager:
366 main()366 main()
367 self.assertEqual(1, context_manager.exception.code)367 self.assertEqual('1', str(context_manager.exception))
368 self.assertEqual(368 self.assertEqual(
369 'Configfile NOT_A_FILE does not exist\n',369 'Configfile NOT_A_FILE does not exist\n',
370 m_stderr.getvalue())370 m_stderr.getvalue())

Subscribers

People subscribed via source and target branches