Merge lp:~dpb/latch-test/archive-artifacts-1493373 into lp:latch-test

Proposed by David Britton
Status: Merged
Approved by: David Britton
Approved revision: 21
Merged at revision: 13
Proposed branch: lp:~dpb/latch-test/archive-artifacts-1493373
Merge into: lp:latch-test
Diff against target: 332 lines (+156/-22)
5 files modified
README (+4/-2)
config (+1/-0)
fixtures/config (+6/-0)
latch.py (+44/-16)
test_latch.py (+101/-4)
To merge this branch: bzr merge lp:~dpb/latch-test/archive-artifacts-1493373
Reviewer Review Type Date Requested Status
Alberto Donato (community) Approve
Adam Collard (community) Approve
🤖 Landscape Builder test results Approve
Review via email: mp+272864@code.launchpad.net

Commit message

Respect 'artifacts' and 'artifacts_dest' tarmac config settings for latch.

Description of the change

Respect 'artifacts' and 'artifacts_dest' tarmac config settings for latch.

To post a comment you must log in.
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :
review: Approve (test results)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :
review: Approve (test results)
Revision history for this message
Adam Collard (adam-collard) wrote :

Minor nit picks inline.

review: Approve
Revision history for this message
Alberto Donato (ack) wrote :

+1. Looks good, a few minor comments inline.

Revision history for this message
Alberto Donato (ack) :
review: Approve
Revision history for this message
David Britton (dpb) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README'
2--- README 2015-02-23 16:38:44 +0000
3+++ README 2015-10-01 16:17:18 +0000
4@@ -41,6 +41,10 @@
5 * [Sections] - correspond to target branches, scanned for landing_candidates
6 (merge proposals).
7 * verify_command - command to run to test the branch.
8+ * artifacts - space separated list of artifacts to save. Paths should be
9+ relative to the branch root.
10+ * artifacts_dest - directory name in CWD where artifacts will be copied to.
11+ if unspecified, `latch-artifacts` is used.
12
13 Everything outside this will be ignored for now.
14
15@@ -59,8 +63,6 @@
16 builds on other projects' functionality in jenkins, combined with the
17 'parameters' file of that feature.
18
19-In the future, more integration will be added.
20-
21
22 Exported Environment
23 --------------------
24
25=== modified file 'config'
26--- config 2015-02-18 00:05:22 +0000
27+++ config 2015-10-01 16:17:18 +0000
28@@ -4,3 +4,4 @@
29 verify_command = make test
30
31 [lp:latch-test]
32+artifacts = config
33
34=== added directory 'fixtures'
35=== added directory 'fixtures/artifacts'
36=== added directory 'fixtures/artifacts/dir1'
37=== added file 'fixtures/artifacts/dir1/file1'
38=== added file 'fixtures/artifacts/dir1/file2'
39=== added directory 'fixtures/artifacts/dir2'
40=== added file 'fixtures/artifacts/file1'
41=== added file 'fixtures/artifacts/file2'
42=== added file 'fixtures/config'
43--- fixtures/config 1970-01-01 00:00:00 +0000
44+++ fixtures/config 2015-10-01 16:17:18 +0000
45@@ -0,0 +1,6 @@
46+# Example configuration for latch pointed at lp:latch-test, expected that you will
47+# customize this for your project.
48+[DEFAULT]
49+verify_command = make test
50+
51+[lp:latch-test]
52
53=== modified file 'latch.py'
54--- latch.py 2015-09-10 13:30:35 +0000
55+++ latch.py 2015-10-01 16:17:18 +0000
56@@ -95,12 +95,6 @@
57 stream=sys.stderr, level=level,
58 format='%(asctime)s %(levelname)s %(message)s')
59
60- def _parse_config(self):
61- """Parse the configuration, return config object."""
62- config = ConfigParser.ConfigParser()
63- config.read(join(dirname(abspath(__file__)), "config"))
64- return config
65-
66 def scan_branch(self, branch):
67 """Scan all MPs for given branch, return candidates to be tested."""
68 lp_candidates = []
69@@ -139,15 +133,22 @@
70 self._lp_login(credentials_file=args.credentials_file)
71 return args
72
73- def _get_config(self):
74- config = ConfigParser.ConfigParser()
75- config.read(join(dirname(abspath(__file__)), "config"))
76+ def _get_config(self, config_file=None):
77+ """Parse the configuration, return config object."""
78+ if config_file is None:
79+ config_file = join(dirname(abspath(__file__)), "config")
80+ config = ConfigParser.ConfigParser({
81+ "artifacts_dest": "latch-artifacts",
82+ "artifacts": ""})
83+ config.read(config_file)
84 return config
85
86- def _lookup_config(self, config, lp_candidate, option):
87- """Return a config in the context of the lp_candidate."""
88+ def _lookup_config(self, lp_candidate, option, config=None):
89+ """Return a config setting in the context of the lp_candidate."""
90+ if config is None:
91+ config = self._get_config()
92 bzr_id = lp_candidate.target_branch.bzr_identity
93- if bzr_id in config.sections():
94+ if config.has_section(bzr_id):
95 try:
96 return config.get(bzr_id, option)
97 except ConfigParser.NoOptionError:
98@@ -176,16 +177,20 @@
99 "candidate", metavar="CANDIDATE",
100 help="Candidate api url link to test.")
101 args = self._parse_args(parser)
102- config = self._get_config()
103 lp_candidate = self.lp.load(args.candidate)
104 try:
105 verify_command = self._lookup_config(
106- config, lp_candidate, "verify_command")
107+ lp_candidate, "verify_command")
108+ artifacts = self._lookup_config(
109+ lp_candidate, "artifacts").split()
110+ artifacts_dest = self._lookup_config(
111+ lp_candidate, "artifacts_dest")
112 except MissingConfigurationError:
113 sys.exit(1)
114 candidate = Candidate(self.lp, lp_candidate, readonly=self.readonly)
115 environment = TestEnvironment(
116- candidate, self.lp, verify_command)
117+ candidate, self.lp, verify_command, artifacts=artifacts,
118+ artifacts_dest=artifacts_dest)
119 with temporary_directory() as temp_dir:
120 environment.initialize(temp_dir)
121 result = environment.execute_test()
122@@ -306,7 +311,9 @@
123
124
125 class TestEnvironment(object):
126- def __init__(self, candidate, lp, verify_command="make", environ=None):
127+ def __init__(
128+ self, candidate, lp, verify_command="make", environ=None,
129+ artifacts=None, artifacts_dest=None):
130 self.candidate = candidate
131 self.lp_candidate = candidate.lp_candidate
132 self.lp = lp
133@@ -314,6 +321,11 @@
134 self.environ = environ
135 if environ is None:
136 self.environ = os.environ
137+ self.artifacts = artifacts if artifacts else []
138+ if artifacts_dest is None:
139+ self.artifacts_dest = os.path.join(os.getcwd(), "latch-artifacts")
140+ else:
141+ self.artifacts_dest = artifacts_dest
142
143 def initialize(self, root_dir):
144 """
145@@ -378,8 +390,24 @@
146 result["status"] = u"Success"
147 return result
148
149+ def _copy_artifacts(self):
150+ """Copy artifacts from self.source_dir into self.artifacts_dest."""
151+ for artifact in self.artifacts:
152+ artifact_src = os.path.join(self.source_dir, artifact)
153+ artifact_dst = os.path.join(self.artifacts_dest, artifact)
154+ log.debug("Copy from %s to %s" % (artifact_src, artifact_dst))
155+ if not os.path.exists(artifact_src):
156+ log.warning("Missing artifact: %s" % artifact_src)
157+ elif os.path.isdir(artifact_src):
158+ shutil.copytree(artifact_src, artifact_dst)
159+ else:
160+ if not os.path.exists(os.path.dirname(artifact_dst)):
161+ os.makedirs(os.path.dirname(artifact_dst))
162+ shutil.copy(artifact_src, artifact_dst)
163+
164 def execute_test(self):
165 """Test the local copy of the code, report on results in the MP."""
166 result = self._verify()
167+ self._copy_artifacts()
168 self.candidate.add_result(result)
169 return result
170
171=== modified file 'test_latch.py'
172--- test_latch.py 2015-09-10 13:30:35 +0000
173+++ test_latch.py 2015-10-01 16:17:18 +0000
174@@ -1,13 +1,16 @@
175 # -*- coding: utf-8 -*-
176 import ConfigParser
177 import latch
178+import filecmp
179 import unittest
180 from mock import MagicMock, patch
181 import logging
182 from logging.handlers import BufferingHandler
183 import os
184 import random
185+import shutil
186 from subprocess import PIPE, STDOUT, check_call, CalledProcessError
187+import tempfile
188
189 OUTPUT = u"output ✈"
190 MULTILINE_OUTPUT = u"multiline\noutput ✈"
191@@ -204,7 +207,7 @@
192 config = ConfigParser.ConfigParser({"key": "value"})
193 config.add_section("target_branch_id")
194 self.assertEqual(
195- self.latch._lookup_config(config, candidate, "key"),
196+ self.latch._lookup_config(candidate, "key", config=config),
197 "value")
198
199 def test__lookup_config_no_section(self):
200@@ -215,7 +218,7 @@
201 with self.assertRaisesRegexp(
202 latch.MissingConfigurationError,
203 ".*target_branch_id not in my configuration.*"):
204- self.latch._lookup_config(config, candidate, "key")
205+ self.latch._lookup_config(candidate, "key", config=config)
206
207 def test__lookup_config_no_key(self):
208 """section exists, key/value does not."""
209@@ -225,7 +228,18 @@
210 with self.assertRaisesRegexp(
211 latch.MissingConfigurationError,
212 ".*key not defined.*"):
213- self.latch._lookup_config(config, candidate, "missing_key")
214+ self.latch._lookup_config(candidate, "missing_key", config=config)
215+
216+ def test__get_config_defaults_are_set(self):
217+ """_get_config has default settings applied."""
218+ config_file = os.path.join(
219+ os.path.dirname(__file__), "fixtures/config")
220+ config = self.latch._get_config(config_file=config_file)
221+ candidate = "lp:latch-test"
222+ self.assertEqual(config.get(candidate, "artifacts"), "")
223+ self.assertEqual(
224+ config.get(candidate, "artifacts_dest"),
225+ "latch-artifacts")
226
227
228 class CandidateTest(LoggedTest):
229@@ -432,15 +446,23 @@
230
231 class TestEnvironmentTest(unittest.TestCase):
232 def setUp(self):
233+ self.tempcwd = tempfile.mkdtemp()
234+ self.oldcwd = os.getcwd()
235+ os.chdir(self.tempcwd)
236 self.lp = MagicMock()
237 self.lp.me = FAKE_ME
238 self.environment = latch.TestEnvironment(
239 latch.Candidate(self.lp, get_candidate(self.lp.me.name)),
240 self.lp,
241 verify_command="fake",
242- environ={})
243+ environ={},
244+ artifacts=[".bzr"])
245 self.environment.source_dir = "source_dir"
246
247+ def tearDown(self):
248+ os.chdir(self.oldcwd)
249+ shutil.rmtree(self.tempcwd)
250+
251 def test_initialize(self):
252 """
253 Call the TestEnvironment initialize() method, verify that
254@@ -516,3 +538,78 @@
255 vote=candidate.vote_lookup[result["status"]],
256 subject="Latch Results: %(branch)s r%(revno)s" % result,
257 review_type="test results")
258+ self.assertIn("latch-artifacts", os.listdir(os.getcwd()))
259+ self.assertEqual(
260+ os.listdir(os.path.join(os.getcwd(), "latch-artifacts")),
261+ [".bzr"])
262+
263+
264+class TestArtifactCopy(unittest.TestCase):
265+
266+ def _get_fake_test_environment(self, artifacts=None, artifacts_dest=None):
267+ """Return a fake test environment, ready to use."""
268+ lp = MagicMock()
269+ env = latch.TestEnvironment(
270+ latch.Candidate(lp, get_candidate(lp.me.name)),
271+ lp,
272+ verify_command="fake",
273+ environ={},
274+ artifacts=artifacts,
275+ artifacts_dest=artifacts_dest)
276+ env.source_dir = os.path.join(
277+ os.path.dirname(__file__), "fixtures/artifacts")
278+ return env
279+
280+ def setUp(self):
281+ # Change my CWD to a tempdir
282+ self.tempdir = tempfile.mkdtemp()
283+ self.oldcwd = os.getcwd()
284+ os.chdir(self.tempdir)
285+
286+ def tearDown(self):
287+ os.chdir(self.oldcwd)
288+ shutil.rmtree(self.tempdir)
289+
290+ def test_run_empty_config(self):
291+ """No artifacts requested, should not create anything."""
292+ env = self._get_fake_test_environment()
293+ env._copy_artifacts()
294+ self.assertEqual(os.listdir(os.getcwd()), [])
295+
296+ def test_run_copy_file_change_dest(self):
297+ """Copy a single sample file, change the dest directory name"""
298+ env = self._get_fake_test_environment(["file2"], "foo-my-dest")
299+ env._copy_artifacts()
300+ self.assertEqual(os.listdir(os.getcwd()), ["foo-my-dest"])
301+ self.assertEqual(
302+ os.listdir(os.path.join(os.getcwd(), "foo-my-dest")),
303+ ["file2"])
304+
305+ def test_run_copy_all(self):
306+ """Copy all components to check files, directories, multiples.
307+
308+ Also use the default dest directory name."""
309+ env = self._get_fake_test_environment([
310+ "file1", "file2", "dir1", "dir2"])
311+ env._copy_artifacts()
312+ comparison = filecmp.dircmp(
313+ env.source_dir,
314+ os.path.join(os.getcwd(), "latch-artifacts"))
315+ self.assertItemsEqual(comparison.common_files, ['file1', 'file2'])
316+ self.assertItemsEqual(comparison.common_dirs, ['dir1', 'dir2'])
317+ self.assertItemsEqual(comparison.diff_files, [])
318+ self.assertItemsEqual(comparison.left_only, [])
319+ self.assertItemsEqual(comparison.right_only, [])
320+
321+ def test_run_copy_missing_artifact(self):
322+ """Copy a single sample file, that is not there.
323+
324+ No artifacts should be copied, warning logged."""
325+ log_mock = MagicMock()
326+ latch.log = log_mock
327+ env = self._get_fake_test_environment(["foo-file-not-found"])
328+ src_path = os.path.join(env.source_dir, "foo-file-not-found")
329+ env._copy_artifacts()
330+ self.assertEqual(os.listdir(os.getcwd()), [])
331+ log_mock.warning.assert_called_once_with(
332+ "Missing artifact: %s" % src_path)

Subscribers

People subscribed via source and target branches

to all changes: