Merge lp:~fginther/cupstream2distro-config/stack-lookup-3 into lp:cupstream2distro-config

Proposed by Francis Ginther
Status: Rejected
Rejected by: Loïc Minier
Proposed branch: lp:~fginther/cupstream2distro-config/stack-lookup-3
Merge into: lp:cupstream2distro-config
Diff against target: 756 lines (+742/-0)
3 files modified
c2dconfigutils/cu2dStackLookup.py (+247/-0)
ci/cu2d-stack-lookup (+40/-0)
tests/test_cu2dStackLookup.py (+455/-0)
To merge this branch: bzr merge lp:~fginther/cupstream2distro-config/stack-lookup-3
Reviewer Review Type Date Requested Status
Chris Gagnon (community) Needs Fixing
PS Jenkins bot continuous-integration Approve
Review via email: mp+169518@code.launchpad.net

Commit message

Provides an API and a utility to query a stack for project, job names and attributes.

Description of the change

Provides an API and a utility to query a stack for project, job names and attributes.

This will be used by the dashboard (it's currently using an earlier version).

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Chris Gagnon (chris.gagnon) wrote :

the cli options -d and -b need tests

Revision history for this message
Chris Gagnon (chris.gagnon) wrote :

I am trying to lookup a children in a stack and get the following

./cu2d-stack-lookup -l -a -i ../stacks/phablet/qt.cfg
["qt-folks", "qtubuntu-gps"]
2013-06-19 17:07:21,049 ERROR Could not find None in stack configuration

Revision history for this message
Chris Gagnon (chris.gagnon) wrote :

I get the same error without the i
./cu2d-stack-lookup -l ../stacks/phablet/qt.cfg -a
["qt-folks", "qtubuntu-gps"]
2013-06-19 17:20:38,097 ERROR Could not find None in stack configuration

Revision history for this message
Chris Gagnon (chris.gagnon) wrote :

I get an error when using -t with -l

./cu2d-stack-lookup -t qt-folks -l ../stacks/phablet/qt.cfg
["qt-folks", "qtubuntu-gps"]
2013-06-19 17:24:58,095 ERROR Could not find qt-folks in stack configuration

Revision history for this message
Chris Gagnon (chris.gagnon) :
review: Needs Fixing
Revision history for this message
Francis Ginther (fginther) wrote :

I'm going to rework this due to a change in direction from the dashboard project. We can't install this in the production environment. My solution is to add a utility to post the information to a well known jenkins job so that the dashboard can consume the data directly from jenkins.

425. By Francis Ginther

Add option to choose output format (yaml or json) and an option to dump a stack summary (to be used by the dashboard tools).

426. By Francis Ginther

Changed project list to a dictionary and fixed error caused by an empty project list.

Revision history for this message
Loïc Minier (lool) wrote :

(Rejecting to drop out of active reviews)

Unmerged revisions

426. By Francis Ginther

Changed project list to a dictionary and fixed error caused by an empty project list.

425. By Francis Ginther

Add option to choose output format (yaml or json) and an option to dump a stack summary (to be used by the dashboard tools).

424. By Francis Ginther

Provides an API and a utility to query a stack for project, job names and attributes.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'c2dconfigutils/cu2dStackLookup.py'
2--- c2dconfigutils/cu2dStackLookup.py 1970-01-01 00:00:00 +0000
3+++ c2dconfigutils/cu2dStackLookup.py 2013-07-16 19:36:23 +0000
4@@ -0,0 +1,247 @@
5+"""Supply information about projects and job names within a stack
6+
7+- Reads stack configuration from YAML configuration file
8+- Returns list of projects within a stack, job names for a project or
9+ specific attributes of a project
10+
11+"""
12+# Copyright (C) 2013, Canonical Ltd (http://www.canonical.com/)
13+#
14+# Author: Francis Ginther <francis.ginther@canonical.com>
15+#
16+# This software is free software: you can redistribute it
17+# and/or modify it under the terms of the GNU General Public License
18+# as published by the Free Software Foundation, either version 3 of
19+# the License, or (at your option) any later version.
20+#
21+# This software is distributed in the hope that it will
22+# be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
23+# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24+# GNU General Public License for more details.
25+#
26+# You should have received a copy of the GNU General Public License
27+# along with this software. If not, see <http://www.gnu.org/licenses/>.
28+
29+import argparse
30+import copy
31+import logging
32+import json
33+import os.path
34+import yaml
35+
36+from c2dconfigutils import (
37+ dict_union, load_default_cfg, load_stack_cfg, set_logging,
38+ get_ci_base_job_name)
39+
40+
41+class StackLookup(object):
42+
43+ DEFAULT_BASE_PATH = '/usr/share/cupstream2distro-config/ci'
44+ SUPPORTED_FORMATS = ['json', 'yaml']
45+ STACK_KEYS = ['name', 'series', 'ppa', 'dest']
46+
47+ def parse_arguments(self, base_path):
48+ """Parses the command line arguments
49+
50+ return args: the parsed arguments
51+ """
52+ parser = argparse.ArgumentParser(
53+ description='Generates jenkins job names from a stack')
54+ parser.add_argument('-d', '--debug', action='store_true',
55+ default=False, help='enable debug mode')
56+ parser.add_argument('-b', '--base_path',
57+ default=base_path,
58+ help='path to the cupstream2distro-config '
59+ 'base directory (default: %s)' % base_path)
60+ parser.add_argument('-o', '--output-format',
61+ default='yaml',
62+ help='Output format to use {}'.format(
63+ self.SUPPORTED_FORMATS))
64+ parser.add_argument('-s', '--summary', action='store_true',
65+ help='Return a stack dump summary')
66+ parser.add_argument('-l', '--list-projects', action='store_true',
67+ default=False, help='return a list of projects '
68+ 'defined within the stack.')
69+ parser.add_argument('-p', '--project',
70+ help='Reference the given project')
71+ parser.add_argument('-a', '--autoland', action='store_true',
72+ default=False,
73+ help='Return the autolanding jobs for a project')
74+ parser.add_argument('-c', '--ci', action='store_true',
75+ default=False,
76+ help='Return the ci jobs for a project')
77+ parser.add_argument('-t', '--attribute',
78+ help='Return the value of the given attribute')
79+ parser.add_argument('-i', '--children', action='store_true',
80+ default=False,
81+ help='Return the child jobs for a project '
82+ '(requires -c/--ci or -a/--autolanding)')
83+ parser.add_argument('stackcfg',
84+ help='Path to a configuration file for the stack')
85+ args = parser.parse_args()
86+ if (not args.list_projects and args.project is None and
87+ not args.summary):
88+ parser.error('Must specify one of -l/--list-projects, '
89+ '-s/--summary or -p/--project')
90+ return False
91+ if (args.project and (args.attribute is None and not args.ci and
92+ not args.autoland)):
93+ parser.error('Must specify -a/--autolanding, -c/--ci or '
94+ '-t/--attribute with -p/--project')
95+ return False
96+ if (args.children and (not args.ci and not args.autoland)):
97+ parser.error('Must specify -a/--autolanding or -c/--ci with '
98+ '-i/--children')
99+ return False
100+ if args.output_format not in self.SUPPORTED_FORMATS:
101+ parser.error('Output format must be one of: {}'.format(
102+ self.SUPPORTED_FORMATS))
103+ return args
104+
105+ def _generate_name(self, project_name, project_config, job_type, children):
106+ job_base_name = get_ci_base_job_name(project_name, project_config)
107+ if children and 'configurations' in project_config:
108+ names = list()
109+ configurations = project_config.pop('configurations')
110+ for config_name in configurations:
111+ if not configurations[config_name]:
112+ # configuration is explicitly removed
113+ continue
114+ names.append('-'.join([job_base_name, config_name, job_type]))
115+ return names
116+ return ['-'.join([job_base_name, job_type])]
117+
118+ def _prepare_project(self, stack, project_name, job_type):
119+ if project_name not in stack['projects']:
120+ raise KeyError
121+ project_config = copy.deepcopy(stack['ci_default'])
122+ dict_union(project_config, stack['projects'][project_name])
123+ job_only_dict = dict()
124+ if job_type in project_config:
125+ job_only_dict = project_config.pop(job_type)
126+ if project_config.get('%s_template' % (job_type), False):
127+ job_dict = copy.deepcopy(project_config)
128+ if job_only_dict is not None:
129+ dict_union(job_dict, job_only_dict)
130+ return job_dict
131+ return None
132+
133+ def _get_name(self, stack, project_name, job_type, children):
134+ try:
135+ job_dict = self._prepare_project(stack, project_name, job_type)
136+ except KeyError:
137+ return False
138+ if job_dict:
139+ return self._generate_name(project_name, job_dict, job_type, children)
140+ return list()
141+
142+ def get_projects(self, stack):
143+ return sorted([project for project in stack['projects']])
144+
145+ def get_ci_name(self, stack, project, children=False):
146+ return self._get_name(stack, project, 'ci', children)
147+
148+ def get_autolanding_name(self, stack, project, children=False):
149+ return self._get_name(stack, project, 'autolanding', children)
150+
151+ def get_attribute(self, stack, project_name, job_type, attribute):
152+ try:
153+ project = self._prepare_project(stack, project_name, job_type)
154+ except KeyError:
155+ return False
156+ if attribute == 'target_branch' and 'target_branch' not in project:
157+ value = "lp:" + project_name
158+ else:
159+ value = project[attribute]
160+ return value
161+
162+ def dump_stack(self, file_name, stack):
163+ out_data = {'projects': {}}
164+
165+ # Try to determine the following pieces from the file name
166+ (head, out_data['file_name']) = os.path.split(file_name)
167+ (head, out_data['release']) = os.path.split(head)
168+ for key in stack:
169+ data = stack[key]
170+ if key in self.STACK_KEYS:
171+ out_data[key] = data
172+
173+ if 'projects' in stack and stack['projects']:
174+ for project in stack['projects']:
175+ config = {}
176+ config['target_branch'] = self.get_attribute(stack, project,
177+ 'autolanding',
178+ 'target_branch')
179+ config['ci'] = self.get_ci_name(stack, project)[0]
180+ config['autolanding'] = self.get_autolanding_name(stack,
181+ project)[0]
182+ out_data['projects'][project] = config
183+
184+ return out_data
185+
186+ def output(self, output_format, data):
187+ if output_format == 'json':
188+ return json.dumps(data)
189+ if output_format == 'yaml':
190+ return yaml.dump(data)
191+ raise RuntimeError('Unknown output format')
192+
193+ def __call__(self, default_config_path):
194+ """Entry point for cu2d-stack-info"""
195+ args = self.parse_arguments(default_config_path)
196+ if not args:
197+ return 1
198+
199+ set_logging(args.debug)
200+ default_config = load_default_cfg(args.base_path)
201+ stackcfg = load_stack_cfg(args.stackcfg, default_config)
202+ if not stackcfg:
203+ logging.error('Stack configuration failed to load. Aborting!')
204+ return 1
205+ if 'projects' not in stackcfg:
206+ logging.error('No projects found in stack configuration')
207+ return 1
208+
209+ if args.list_projects:
210+ projects = self.get_projects(stackcfg)
211+ if not projects:
212+ return 1
213+ print(self.output(args.output_format, sorted(projects)))
214+
215+ if args.summary:
216+ data_dump = self.dump_stack(args.stackcfg, stackcfg)
217+ if not data_dump:
218+ return 1
219+ print(self.output(args.output_format, data_dump))
220+
221+ if args.attribute:
222+ job_type = 'autolanding'
223+ if args.ci:
224+ job_type = 'ci'
225+ value = self.get_attribute(stackcfg, args.project, job_type,
226+ args.attribute)
227+ if not value:
228+ logging.error("Could not find %s in stack configuration" %
229+ args.attribute)
230+ return 1
231+ print(self.output(args.output_format, value))
232+ return 0
233+
234+ if args.ci:
235+ name = self.get_ci_name(stackcfg, args.project, args.children)
236+ if not name:
237+ logging.error("Could not find %s in stack configuration" %
238+ args.project)
239+ return 1
240+ print(self.output(args.output_format, name))
241+ return 0
242+
243+ if args.autoland:
244+ name = self.get_autolanding_name(stackcfg, args.project,
245+ args.children)
246+ if not name:
247+ logging.error("Could not find %s in stack configuration" %
248+ args.project)
249+ return 1
250+ print(self.output(args.output_format, name))
251+ return 0
252
253=== added file 'ci/cu2d-stack-lookup'
254--- ci/cu2d-stack-lookup 1970-01-01 00:00:00 +0000
255+++ ci/cu2d-stack-lookup 2013-07-16 19:36:23 +0000
256@@ -0,0 +1,40 @@
257+#!/usr/bin/env python
258+"""Supply information about projects and job names within a stack
259+
260+- Reads stack configuration from YAML configuration file
261+- Returns list of projects within a stack, jobs names for a project or
262+ specific attributes of a project
263+
264+"""
265+# Copyright (C) 2013, Canonical Ltd (http://www.canonical.com/)
266+#
267+# Author: Francis Ginther <francis.ginther@canonical.com>
268+#
269+# This software is free software: you can redistribute it
270+# and/or modify it under the terms of the GNU General Public License
271+# as published by the Free Software Foundation, either version 3 of
272+# the License, or (at your option) any later version.
273+#
274+# This software is distributed in the hope that it will
275+# be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
276+# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
277+# GNU General Public License for more details.
278+#
279+# You should have received a copy of the GNU General Public License
280+# along with this software. If not, see <http://www.gnu.org/licenses/>.
281+
282+import sys
283+import os
284+
285+BASEDIR = os.path.dirname(__file__)
286+
287+# add local c2configutils
288+if os.path.isdir(os.path.join(BASEDIR, '../c2dconfigutils')):
289+ sys.path.insert(0, os.path.join(BASEDIR, '..'))
290+
291+from c2dconfigutils.cu2dStackLookup import StackLookup
292+
293+
294+if __name__ == "__main__":
295+ command = StackLookup()
296+ sys.exit(command(BASEDIR))
297
298=== added file 'tests/test_cu2dStackLookup.py'
299--- tests/test_cu2dStackLookup.py 1970-01-01 00:00:00 +0000
300+++ tests/test_cu2dStackLookup.py 2013-07-16 19:36:23 +0000
301@@ -0,0 +1,455 @@
302+from unittest import TestCase
303+from mock import patch, MagicMock
304+from StringIO import StringIO
305+import sys
306+
307+from c2dconfigutils.cu2dStackLookup import (
308+ StackLookup)
309+
310+
311+class Cu2dStackLookupTestsBase(TestCase):
312+
313+ def setUp(self):
314+ self.stack = {
315+ 'ci_default': {
316+ 'ci_template': 'ci-config.xml.tmpl',
317+ 'autolanding_template': 'autolanding-config.xml.tmpl',
318+ 'build_template': 'pbuilder-config.xml.tmpl',
319+ 'ci': {
320+ 'configurations': {
321+ 'raring-amd64': {
322+ 'node_label': 'pbuilder'},
323+ 'raring-i386': {
324+ 'node_label': 'pbuilder'}}}},
325+ 'projects': {
326+ 'autopilot': {
327+ 'autolanding': {
328+ 'configurations': {
329+ 'quantal-amd64': {
330+ 'node_label': 'pbuilder'},
331+ 'quantal-i386': {
332+ 'node_label': 'pbuilder'}}}},
333+ 'autopilot-qt': {
334+ 'ci_template': False},
335+ 'autopilot-gtk': {
336+ 'autolanding_template': False},
337+ 'xpathselect': {
338+ 'target_branch': 'lp:xpathselect/feature',
339+ 'ci': {
340+ 'configurations': {
341+ 'raring-amd64': False}}}}}
342+ self.lookup = StackLookup()
343+
344+
345+class Cu2dStackLookupMethodTests(Cu2dStackLookupTestsBase):
346+
347+ def test_get_projects(self):
348+ """Verifies all project names are returned"""
349+ expected = ['autopilot', 'autopilot-gtk', 'autopilot-qt',
350+ 'xpathselect']
351+ actual = self.lookup.get_projects(self.stack)
352+ self.assertEqual(expected, actual)
353+
354+ def test_get_name_ci(self):
355+ """Verifies a ci name is generated"""
356+ expected = ['autopilot-ci']
357+ actual = self.lookup._get_name(self.stack, 'autopilot', 'ci', False)
358+ self.assertEqual(expected, actual)
359+
360+ def test_get_name_autolanding(self):
361+ """Verifies an autolanding name is generated"""
362+ expected = ['autopilot-autolanding']
363+ actual = self.lookup._get_name(self.stack, 'autopilot', 'autolanding',
364+ False)
365+ self.assertEqual(expected, actual)
366+
367+ def test_get_name_target_branch(self):
368+ """Verifies a name is generated based on the target branch"""
369+ expected = ['xpathselect-feature-ci']
370+ actual = self.lookup._get_name(self.stack, 'xpathselect', 'ci', False)
371+ self.assertEqual(expected, actual)
372+
373+ def test_get_name_no_template(self):
374+ """Verifies that no names are returned from a False template"""
375+ expected = []
376+ actual = self.lookup._get_name(self.stack, 'autopilot-qt', 'ci', False)
377+ self.assertEqual(expected, actual)
378+
379+ def test_get_name_not_found(self):
380+ """Verifies an invalid project returns no names"""
381+ expected = False
382+ actual = self.lookup._get_name(self.stack, 'not-found', 'ci', False)
383+ self.assertEqual(expected, actual)
384+
385+ def test_get_ci_name(self):
386+ """Verifies a ci job is returned for a valid project"""
387+ expected = ['autopilot-gtk-ci']
388+ actual = self.lookup.get_ci_name(self.stack, 'autopilot-gtk')
389+ self.assertEqual(expected, actual)
390+
391+ def test_get_ci_children(self):
392+ """Verifies ci children jobs are returned for a valid project"""
393+ expected = ['autopilot-raring-amd64-ci',
394+ 'autopilot-raring-i386-ci']
395+ actual = self.lookup.get_ci_name(self.stack, 'autopilot',
396+ children=True)
397+ self.assertEqual(expected, actual)
398+
399+ def test_get_ci_children_default_removed(self):
400+ """Verifies a project can overide a default configuration"""
401+ expected = ['xpathselect-feature-raring-i386-ci']
402+ actual = self.lookup.get_ci_name(self.stack, 'xpathselect',
403+ children=True)
404+ self.assertEqual(expected, actual)
405+
406+ def test_get_autolanding_name(self):
407+ """Verifies an autolanding job is returned for a valid project"""
408+ expected = ['autopilot-autolanding']
409+ actual = self.lookup.get_autolanding_name(self.stack, 'autopilot')
410+ self.assertEqual(expected, actual)
411+
412+ def test_get_autolanding_children(self):
413+ """Verifies autolanding children are returned for a valid project"""
414+ expected = ['autopilot-quantal-i386-autolanding',
415+ 'autopilot-quantal-amd64-autolanding']
416+ actual = self.lookup.get_autolanding_name(self.stack, 'autopilot',
417+ children=True)
418+ self.assertEqual(expected, actual)
419+
420+ def test_get_autolanding_children_no_template(self):
421+ """Verifies that no children are returned from a False template"""
422+ expected = []
423+ actual = self.lookup.get_autolanding_name(self.stack, 'autopilot-gtk',
424+ children=True)
425+ self.assertEqual(expected, actual)
426+
427+ def test_get_attribute_default_target_branch(self):
428+ """Verifies the default target branch is returned"""
429+ expected = 'lp:autopilot'
430+ actual = self.lookup.get_attribute(self.stack, 'autopilot',
431+ 'autolanding', 'target_branch')
432+ self.assertEqual(expected, actual)
433+
434+ def test_get_attribute_target_branch(self):
435+ """Verifies the stack specified target branch is returned"""
436+ expected = 'lp:xpathselect/feature'
437+ actual = self.lookup.get_attribute(self.stack, 'xpathselect', 'ci',
438+ 'target_branch')
439+ self.assertEqual(expected, actual)
440+
441+ def test_get_attribute_autolanding_value(self):
442+ """Verifies the autolanding attribute value is returned"""
443+ expected = {
444+ 'quantal-amd64': {
445+ 'node_label': 'pbuilder'},
446+ 'quantal-i386': {
447+ 'node_label': 'pbuilder'}}
448+ actual = self.lookup.get_attribute(self.stack, 'autopilot',
449+ 'autolanding', 'configurations')
450+ self.assertEqual(expected, actual)
451+
452+ def test_get_attribute_ci_value(self):
453+ """Verifies the ci attribute value is returned"""
454+ expected = {
455+ 'raring-amd64': {
456+ 'node_label': 'pbuilder'},
457+ 'raring-i386': {
458+ 'node_label': 'pbuilder'}}
459+ actual = self.lookup.get_attribute(self.stack, 'autopilot',
460+ 'ci', 'configurations')
461+ self.assertEqual(expected, actual)
462+
463+ def test_get_attribute_bad_project(self):
464+ """Verifies that a missing project returns False"""
465+ expected = False
466+ actual = self.lookup.get_attribute(self.stack, 'bad_project',
467+ 'ci', 'bad_key')
468+ self.assertEqual(expected, actual)
469+
470+ def test_get_attribute_bad_value(self):
471+ """Verifies that a missing key raises a KeyError"""
472+ with self.assertRaises(KeyError):
473+ self.lookup.get_attribute(self.stack, 'autopilot', 'ci',
474+ 'bad_key')
475+
476+
477+class Cu2dStackLookupCallTests(Cu2dStackLookupTestsBase):
478+
479+ def setUp(self):
480+ super(Cu2dStackLookupCallTests, self).setUp()
481+ self.args = type('Args', (object,), {'debug': False,
482+ 'base_path': '/tmp',
483+ 'list_projects': False,
484+ 'project': None,
485+ 'autoland': False,
486+ 'ci': False,
487+ 'attribute': None,
488+ 'children': False,
489+ 'stackcfg': 'stack.cfg'})
490+ self.lookup.parse_arguments = lambda x: self.args
491+ self.saved_stdout = sys.stdout
492+ self.stdout = StringIO()
493+ sys.stdout = self.stdout
494+
495+ def tearDown(self):
496+ sys.stdout = self.saved_stdout
497+
498+ def test_bad_args(self):
499+ """Verifies error return for invalid arguments"""
500+ expected = 1
501+ self.args = None
502+ actual = self.lookup('/tmp')
503+ self.assertEqual(expected, actual)
504+
505+ def test_no_stack(self):
506+ """Verifies error return for an invalid stack file"""
507+ expected = 1
508+ self.args.list_projects = True
509+ with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
510+ new=lambda x: True), \
511+ patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
512+ new=lambda x, y: {}):
513+ actual = self.lookup('/tmp')
514+ self.assertEqual(expected, actual)
515+
516+ def test_no_projects(self):
517+ """Verifies error return for a stack file with no projects"""
518+ expected = 1
519+ self.args.list_projects = True
520+ with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
521+ new=lambda x: True), \
522+ patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
523+ new=lambda x, y: {'ci_default': {}}):
524+ actual = self.lookup('/tmp')
525+ self.assertEqual(expected, actual)
526+
527+ def test_get_projects(self):
528+ """Verifies output for a project list request"""
529+ expected = 0
530+ self.args.list_projects = True
531+ with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
532+ new=lambda x: True), \
533+ patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
534+ new=lambda x, y: self.stack):
535+ actual = self.lookup('/tmp')
536+ self.assertEqual(expected, actual)
537+ output = '["autopilot", "autopilot-gtk", "autopilot-qt", ' \
538+ '"xpathselect"]'
539+ self.assertEqual(output, self.stdout.getvalue().strip())
540+
541+ def test_get_projects_none_found(self):
542+ """Verifies error return when stack file contains no projects"""
543+ expected = 1
544+ self.args.list_projects = True
545+ with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
546+ new=lambda x: True), \
547+ patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
548+ new=lambda x, y: {'projects': {}}):
549+ actual = self.lookup('/tmp')
550+ self.assertEqual(expected, actual)
551+
552+ def test_get_attribute_ci(self):
553+ """Verifies output for an attribute request"""
554+ expected = 0
555+ self.args.attribute = 'target_branch'
556+ self.args.ci = True
557+ self.args.project = 'xpathselect'
558+ with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
559+ new=lambda x: True), \
560+ patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
561+ new=lambda x, y: self.stack):
562+ actual = self.lookup('/tmp')
563+ self.assertEqual(expected, actual)
564+ output = '"lp:xpathselect/feature"'
565+ self.assertEqual(output, self.stdout.getvalue().strip())
566+
567+ def test_get_attribute_autoland(self):
568+ """Verifies out for an attribute request for an autolanding job"""
569+ expected = 0
570+ self.args.attribute = 'target_branch'
571+ self.args.project = 'xpathselect'
572+ with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
573+ new=lambda x: True), \
574+ patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
575+ new=lambda x, y: self.stack):
576+ actual = self.lookup('/tmp')
577+ self.assertEqual(expected, actual)
578+ output = '"lp:xpathselect/feature"'
579+ self.assertEqual(output, self.stdout.getvalue().strip())
580+
581+ def test_get_attribute_no_project(self):
582+ """Verifies error return when the project is not found"""
583+ expected = 1
584+ self.args.attribute = 'target_branch'
585+ self.args.project = 'not-found'
586+ with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
587+ new=lambda x: True), \
588+ patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
589+ new=lambda x, y: self.stack):
590+ actual = self.lookup('/tmp')
591+ self.assertEqual(expected, actual)
592+
593+ def test_get_ci_name(self):
594+ """Verifies output for a ci job list request"""
595+ expected = 0
596+ self.args.ci = True
597+ self.args.project = 'autopilot'
598+ with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
599+ new=lambda x: True), \
600+ patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
601+ new=lambda x, y: self.stack):
602+ actual = self.lookup('/tmp')
603+ self.assertEqual(expected, actual)
604+ output = '["autopilot-ci"]'
605+ self.assertEqual(output, self.stdout.getvalue().strip())
606+
607+ def test_get_ci_name_no_project(self):
608+ """Verifies error return when the project is not found"""
609+ expected = 1
610+ self.args.ci = True
611+ self.args.project = 'not-found'
612+ with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
613+ new=lambda x: True), \
614+ patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
615+ new=lambda x, y: self.stack):
616+ actual = self.lookup('/tmp')
617+ self.assertEqual(expected, actual)
618+
619+ def test_get_autolanding_name(self):
620+ """Verifies output for an autolanding job list request"""
621+ expected = 0
622+ self.args.autoland = True
623+ self.args.project = 'autopilot'
624+ with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
625+ new=lambda x: True), \
626+ patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
627+ new=lambda x, y: self.stack):
628+ actual = self.lookup('/tmp')
629+ self.assertEqual(expected, actual)
630+ output = '["autopilot-autolanding"]'
631+ self.assertEqual(output, self.stdout.getvalue().strip())
632+
633+ def test_get_autolanding_name_no_project(self):
634+ """Verifies error return when the project is not found"""
635+ expected = 1
636+ self.args.autoland = True
637+ self.args.project = 'not-found'
638+ with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
639+ new=lambda x: True), \
640+ patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
641+ new=lambda x, y: self.stack):
642+ actual = self.lookup('/tmp')
643+ self.assertEqual(expected, actual)
644+
645+
646+class TestParseArguments(TestCase):
647+ def setUp(self):
648+ self.error_mock = MagicMock()
649+
650+ def test_gibberish(self):
651+ """Verifies invalid arguments returns an error"""
652+ sys_argv = ['./command', '-oheck']
653+ with patch('sys.argv', sys_argv):
654+ with patch('argparse.ArgumentParser.error', self.error_mock):
655+ lookup = StackLookup()
656+ lookup.parse_arguments('')
657+ self.error_mock.assert_any_call(
658+ 'too few arguments')
659+
660+ def test_noparams(self):
661+ """Verifies no arguments returns a too few arguments error"""
662+ sys_argv = ['./command']
663+ with patch('sys.argv', sys_argv):
664+ with patch('argparse.ArgumentParser.error', self.error_mock):
665+ lookup = StackLookup()
666+ lookup.parse_arguments('')
667+ self.error_mock.assert_any_call(
668+ 'too few arguments')
669+
670+ def test_missing_main_option(self):
671+ """Verify error is returned for a missing primary option"""
672+ sys_argv = ['./command', 'stack.cfg']
673+ with patch('sys.argv', sys_argv):
674+ with patch('argparse.ArgumentParser.error', self.error_mock):
675+ lookup = StackLookup()
676+ lookup.parse_arguments('')
677+ self.error_mock.assert_called_with(
678+ 'Must specify one of -l/--list-projects or -p/--project')
679+
680+ def test_missing_project_option(self):
681+ """Verify error is returned for a missing project option"""
682+ sys_argv = ['./command', '--project', 'foo', 'stack.cfg']
683+ with patch('sys.argv', sys_argv):
684+ with patch('argparse.ArgumentParser.error', self.error_mock):
685+ lookup = StackLookup()
686+ lookup.parse_arguments('')
687+ self.error_mock.assert_called_with(
688+ 'Must specify -a/--autolanding, -c/--ci or '
689+ '-t/--attribute with -p/--project')
690+
691+ def test_missing_project_option_with_children(self):
692+ """Verify error is returned for a missing project and children
693+ option"""
694+ sys_argv = ['./command', '--project', 'foo', '--attribute',
695+ 'target_branch', '--children', 'stack.cfg']
696+ with patch('sys.argv', sys_argv):
697+ with patch('argparse.ArgumentParser.error', self.error_mock):
698+ lookup = StackLookup()
699+ lookup.parse_arguments('')
700+ self.error_mock.assert_called_with(
701+ 'Must specify -a/--autolanding or -c/--ci with '
702+ '-i/--children')
703+
704+ def test_list_projects(self):
705+ """Verify a list projects request"""
706+ sys_argv = ['./command', '-l', 'stack.cfg']
707+ with patch('sys.argv', sys_argv):
708+ lookup = StackLookup()
709+ ret = lookup.parse_arguments('')
710+ self.assertTrue(ret.list_projects)
711+
712+ def test_project_ci(self):
713+ """Verify a ci job request"""
714+ sys_argv = ['./command', '-p', 'project', '-c', 'stack.cfg']
715+ with patch('sys.argv', sys_argv):
716+ lookup = StackLookup()
717+ ret = lookup.parse_arguments('')
718+ self.assertTrue(ret.ci)
719+ self.assertEqual('project', ret.project)
720+
721+ def test_project_autoland(self):
722+ """Verify a ci job request"""
723+ sys_argv = ['./command', '-p', 'project', '-a', 'stack.cfg']
724+ with patch('sys.argv', sys_argv):
725+ lookup = StackLookup()
726+ ret = lookup.parse_arguments('')
727+ self.assertTrue(ret.autoland)
728+ self.assertEqual('project', ret.project)
729+
730+ def test_project_ci_children(self):
731+ """Verify a ci job request"""
732+ sys_argv = ['./command', '-p', 'project', '-c', '-i', 'stack.cfg']
733+ with patch('sys.argv', sys_argv):
734+ lookup = StackLookup()
735+ ret = lookup.parse_arguments('')
736+ self.assertTrue(ret.ci)
737+ self.assertTrue(ret.children)
738+
739+ def test_project_autoland_children(self):
740+ """Verify a ci job request"""
741+ sys_argv = ['./command', '-p', 'project', '-a', '-i', 'stack.cfg']
742+ with patch('sys.argv', sys_argv):
743+ lookup = StackLookup()
744+ ret = lookup.parse_arguments('')
745+ self.assertTrue(ret.autoland)
746+ self.assertTrue(ret.children)
747+
748+ def test_project_attribute(self):
749+ """Verify a ci job request"""
750+ sys_argv = ['./command', '-p', 'project', '-t', 'target_branch',
751+ 'stack.cfg']
752+ with patch('sys.argv', sys_argv):
753+ lookup = StackLookup()
754+ ret = lookup.parse_arguments('')
755+ self.assertEqual('project', ret.project)
756+ self.assertEqual('target_branch', ret.attribute)

Subscribers

People subscribed via source and target branches

to all changes: