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
=== added file 'c2dconfigutils/cu2dStackLookup.py'
--- c2dconfigutils/cu2dStackLookup.py 1970-01-01 00:00:00 +0000
+++ c2dconfigutils/cu2dStackLookup.py 2013-07-16 19:36:23 +0000
@@ -0,0 +1,247 @@
1"""Supply information about projects and job names within a stack
2
3- Reads stack configuration from YAML configuration file
4- Returns list of projects within a stack, job names for a project or
5 specific attributes of a project
6
7"""
8# Copyright (C) 2013, Canonical Ltd (http://www.canonical.com/)
9#
10# Author: Francis Ginther <francis.ginther@canonical.com>
11#
12# This software is free software: you can redistribute it
13# and/or modify it under the terms of the GNU General Public License
14# as published by the Free Software Foundation, either version 3 of
15# the License, or (at your option) any later version.
16#
17# This software is distributed in the hope that it will
18# be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
19# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License
23# along with this software. If not, see <http://www.gnu.org/licenses/>.
24
25import argparse
26import copy
27import logging
28import json
29import os.path
30import yaml
31
32from c2dconfigutils import (
33 dict_union, load_default_cfg, load_stack_cfg, set_logging,
34 get_ci_base_job_name)
35
36
37class StackLookup(object):
38
39 DEFAULT_BASE_PATH = '/usr/share/cupstream2distro-config/ci'
40 SUPPORTED_FORMATS = ['json', 'yaml']
41 STACK_KEYS = ['name', 'series', 'ppa', 'dest']
42
43 def parse_arguments(self, base_path):
44 """Parses the command line arguments
45
46 return args: the parsed arguments
47 """
48 parser = argparse.ArgumentParser(
49 description='Generates jenkins job names from a stack')
50 parser.add_argument('-d', '--debug', action='store_true',
51 default=False, help='enable debug mode')
52 parser.add_argument('-b', '--base_path',
53 default=base_path,
54 help='path to the cupstream2distro-config '
55 'base directory (default: %s)' % base_path)
56 parser.add_argument('-o', '--output-format',
57 default='yaml',
58 help='Output format to use {}'.format(
59 self.SUPPORTED_FORMATS))
60 parser.add_argument('-s', '--summary', action='store_true',
61 help='Return a stack dump summary')
62 parser.add_argument('-l', '--list-projects', action='store_true',
63 default=False, help='return a list of projects '
64 'defined within the stack.')
65 parser.add_argument('-p', '--project',
66 help='Reference the given project')
67 parser.add_argument('-a', '--autoland', action='store_true',
68 default=False,
69 help='Return the autolanding jobs for a project')
70 parser.add_argument('-c', '--ci', action='store_true',
71 default=False,
72 help='Return the ci jobs for a project')
73 parser.add_argument('-t', '--attribute',
74 help='Return the value of the given attribute')
75 parser.add_argument('-i', '--children', action='store_true',
76 default=False,
77 help='Return the child jobs for a project '
78 '(requires -c/--ci or -a/--autolanding)')
79 parser.add_argument('stackcfg',
80 help='Path to a configuration file for the stack')
81 args = parser.parse_args()
82 if (not args.list_projects and args.project is None and
83 not args.summary):
84 parser.error('Must specify one of -l/--list-projects, '
85 '-s/--summary or -p/--project')
86 return False
87 if (args.project and (args.attribute is None and not args.ci and
88 not args.autoland)):
89 parser.error('Must specify -a/--autolanding, -c/--ci or '
90 '-t/--attribute with -p/--project')
91 return False
92 if (args.children and (not args.ci and not args.autoland)):
93 parser.error('Must specify -a/--autolanding or -c/--ci with '
94 '-i/--children')
95 return False
96 if args.output_format not in self.SUPPORTED_FORMATS:
97 parser.error('Output format must be one of: {}'.format(
98 self.SUPPORTED_FORMATS))
99 return args
100
101 def _generate_name(self, project_name, project_config, job_type, children):
102 job_base_name = get_ci_base_job_name(project_name, project_config)
103 if children and 'configurations' in project_config:
104 names = list()
105 configurations = project_config.pop('configurations')
106 for config_name in configurations:
107 if not configurations[config_name]:
108 # configuration is explicitly removed
109 continue
110 names.append('-'.join([job_base_name, config_name, job_type]))
111 return names
112 return ['-'.join([job_base_name, job_type])]
113
114 def _prepare_project(self, stack, project_name, job_type):
115 if project_name not in stack['projects']:
116 raise KeyError
117 project_config = copy.deepcopy(stack['ci_default'])
118 dict_union(project_config, stack['projects'][project_name])
119 job_only_dict = dict()
120 if job_type in project_config:
121 job_only_dict = project_config.pop(job_type)
122 if project_config.get('%s_template' % (job_type), False):
123 job_dict = copy.deepcopy(project_config)
124 if job_only_dict is not None:
125 dict_union(job_dict, job_only_dict)
126 return job_dict
127 return None
128
129 def _get_name(self, stack, project_name, job_type, children):
130 try:
131 job_dict = self._prepare_project(stack, project_name, job_type)
132 except KeyError:
133 return False
134 if job_dict:
135 return self._generate_name(project_name, job_dict, job_type, children)
136 return list()
137
138 def get_projects(self, stack):
139 return sorted([project for project in stack['projects']])
140
141 def get_ci_name(self, stack, project, children=False):
142 return self._get_name(stack, project, 'ci', children)
143
144 def get_autolanding_name(self, stack, project, children=False):
145 return self._get_name(stack, project, 'autolanding', children)
146
147 def get_attribute(self, stack, project_name, job_type, attribute):
148 try:
149 project = self._prepare_project(stack, project_name, job_type)
150 except KeyError:
151 return False
152 if attribute == 'target_branch' and 'target_branch' not in project:
153 value = "lp:" + project_name
154 else:
155 value = project[attribute]
156 return value
157
158 def dump_stack(self, file_name, stack):
159 out_data = {'projects': {}}
160
161 # Try to determine the following pieces from the file name
162 (head, out_data['file_name']) = os.path.split(file_name)
163 (head, out_data['release']) = os.path.split(head)
164 for key in stack:
165 data = stack[key]
166 if key in self.STACK_KEYS:
167 out_data[key] = data
168
169 if 'projects' in stack and stack['projects']:
170 for project in stack['projects']:
171 config = {}
172 config['target_branch'] = self.get_attribute(stack, project,
173 'autolanding',
174 'target_branch')
175 config['ci'] = self.get_ci_name(stack, project)[0]
176 config['autolanding'] = self.get_autolanding_name(stack,
177 project)[0]
178 out_data['projects'][project] = config
179
180 return out_data
181
182 def output(self, output_format, data):
183 if output_format == 'json':
184 return json.dumps(data)
185 if output_format == 'yaml':
186 return yaml.dump(data)
187 raise RuntimeError('Unknown output format')
188
189 def __call__(self, default_config_path):
190 """Entry point for cu2d-stack-info"""
191 args = self.parse_arguments(default_config_path)
192 if not args:
193 return 1
194
195 set_logging(args.debug)
196 default_config = load_default_cfg(args.base_path)
197 stackcfg = load_stack_cfg(args.stackcfg, default_config)
198 if not stackcfg:
199 logging.error('Stack configuration failed to load. Aborting!')
200 return 1
201 if 'projects' not in stackcfg:
202 logging.error('No projects found in stack configuration')
203 return 1
204
205 if args.list_projects:
206 projects = self.get_projects(stackcfg)
207 if not projects:
208 return 1
209 print(self.output(args.output_format, sorted(projects)))
210
211 if args.summary:
212 data_dump = self.dump_stack(args.stackcfg, stackcfg)
213 if not data_dump:
214 return 1
215 print(self.output(args.output_format, data_dump))
216
217 if args.attribute:
218 job_type = 'autolanding'
219 if args.ci:
220 job_type = 'ci'
221 value = self.get_attribute(stackcfg, args.project, job_type,
222 args.attribute)
223 if not value:
224 logging.error("Could not find %s in stack configuration" %
225 args.attribute)
226 return 1
227 print(self.output(args.output_format, value))
228 return 0
229
230 if args.ci:
231 name = self.get_ci_name(stackcfg, args.project, args.children)
232 if not name:
233 logging.error("Could not find %s in stack configuration" %
234 args.project)
235 return 1
236 print(self.output(args.output_format, name))
237 return 0
238
239 if args.autoland:
240 name = self.get_autolanding_name(stackcfg, args.project,
241 args.children)
242 if not name:
243 logging.error("Could not find %s in stack configuration" %
244 args.project)
245 return 1
246 print(self.output(args.output_format, name))
247 return 0
0248
=== added file 'ci/cu2d-stack-lookup'
--- ci/cu2d-stack-lookup 1970-01-01 00:00:00 +0000
+++ ci/cu2d-stack-lookup 2013-07-16 19:36:23 +0000
@@ -0,0 +1,40 @@
1#!/usr/bin/env python
2"""Supply information about projects and job names within a stack
3
4- Reads stack configuration from YAML configuration file
5- Returns list of projects within a stack, jobs names for a project or
6 specific attributes of a project
7
8"""
9# Copyright (C) 2013, Canonical Ltd (http://www.canonical.com/)
10#
11# Author: Francis Ginther <francis.ginther@canonical.com>
12#
13# This software is free software: you can redistribute it
14# and/or modify it under the terms of the GNU General Public License
15# as published by the Free Software Foundation, either version 3 of
16# the License, or (at your option) any later version.
17#
18# This software is distributed in the hope that it will
19# be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
20# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21# GNU General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License
24# along with this software. If not, see <http://www.gnu.org/licenses/>.
25
26import sys
27import os
28
29BASEDIR = os.path.dirname(__file__)
30
31# add local c2configutils
32if os.path.isdir(os.path.join(BASEDIR, '../c2dconfigutils')):
33 sys.path.insert(0, os.path.join(BASEDIR, '..'))
34
35from c2dconfigutils.cu2dStackLookup import StackLookup
36
37
38if __name__ == "__main__":
39 command = StackLookup()
40 sys.exit(command(BASEDIR))
041
=== added file 'tests/test_cu2dStackLookup.py'
--- tests/test_cu2dStackLookup.py 1970-01-01 00:00:00 +0000
+++ tests/test_cu2dStackLookup.py 2013-07-16 19:36:23 +0000
@@ -0,0 +1,455 @@
1from unittest import TestCase
2from mock import patch, MagicMock
3from StringIO import StringIO
4import sys
5
6from c2dconfigutils.cu2dStackLookup import (
7 StackLookup)
8
9
10class Cu2dStackLookupTestsBase(TestCase):
11
12 def setUp(self):
13 self.stack = {
14 'ci_default': {
15 'ci_template': 'ci-config.xml.tmpl',
16 'autolanding_template': 'autolanding-config.xml.tmpl',
17 'build_template': 'pbuilder-config.xml.tmpl',
18 'ci': {
19 'configurations': {
20 'raring-amd64': {
21 'node_label': 'pbuilder'},
22 'raring-i386': {
23 'node_label': 'pbuilder'}}}},
24 'projects': {
25 'autopilot': {
26 'autolanding': {
27 'configurations': {
28 'quantal-amd64': {
29 'node_label': 'pbuilder'},
30 'quantal-i386': {
31 'node_label': 'pbuilder'}}}},
32 'autopilot-qt': {
33 'ci_template': False},
34 'autopilot-gtk': {
35 'autolanding_template': False},
36 'xpathselect': {
37 'target_branch': 'lp:xpathselect/feature',
38 'ci': {
39 'configurations': {
40 'raring-amd64': False}}}}}
41 self.lookup = StackLookup()
42
43
44class Cu2dStackLookupMethodTests(Cu2dStackLookupTestsBase):
45
46 def test_get_projects(self):
47 """Verifies all project names are returned"""
48 expected = ['autopilot', 'autopilot-gtk', 'autopilot-qt',
49 'xpathselect']
50 actual = self.lookup.get_projects(self.stack)
51 self.assertEqual(expected, actual)
52
53 def test_get_name_ci(self):
54 """Verifies a ci name is generated"""
55 expected = ['autopilot-ci']
56 actual = self.lookup._get_name(self.stack, 'autopilot', 'ci', False)
57 self.assertEqual(expected, actual)
58
59 def test_get_name_autolanding(self):
60 """Verifies an autolanding name is generated"""
61 expected = ['autopilot-autolanding']
62 actual = self.lookup._get_name(self.stack, 'autopilot', 'autolanding',
63 False)
64 self.assertEqual(expected, actual)
65
66 def test_get_name_target_branch(self):
67 """Verifies a name is generated based on the target branch"""
68 expected = ['xpathselect-feature-ci']
69 actual = self.lookup._get_name(self.stack, 'xpathselect', 'ci', False)
70 self.assertEqual(expected, actual)
71
72 def test_get_name_no_template(self):
73 """Verifies that no names are returned from a False template"""
74 expected = []
75 actual = self.lookup._get_name(self.stack, 'autopilot-qt', 'ci', False)
76 self.assertEqual(expected, actual)
77
78 def test_get_name_not_found(self):
79 """Verifies an invalid project returns no names"""
80 expected = False
81 actual = self.lookup._get_name(self.stack, 'not-found', 'ci', False)
82 self.assertEqual(expected, actual)
83
84 def test_get_ci_name(self):
85 """Verifies a ci job is returned for a valid project"""
86 expected = ['autopilot-gtk-ci']
87 actual = self.lookup.get_ci_name(self.stack, 'autopilot-gtk')
88 self.assertEqual(expected, actual)
89
90 def test_get_ci_children(self):
91 """Verifies ci children jobs are returned for a valid project"""
92 expected = ['autopilot-raring-amd64-ci',
93 'autopilot-raring-i386-ci']
94 actual = self.lookup.get_ci_name(self.stack, 'autopilot',
95 children=True)
96 self.assertEqual(expected, actual)
97
98 def test_get_ci_children_default_removed(self):
99 """Verifies a project can overide a default configuration"""
100 expected = ['xpathselect-feature-raring-i386-ci']
101 actual = self.lookup.get_ci_name(self.stack, 'xpathselect',
102 children=True)
103 self.assertEqual(expected, actual)
104
105 def test_get_autolanding_name(self):
106 """Verifies an autolanding job is returned for a valid project"""
107 expected = ['autopilot-autolanding']
108 actual = self.lookup.get_autolanding_name(self.stack, 'autopilot')
109 self.assertEqual(expected, actual)
110
111 def test_get_autolanding_children(self):
112 """Verifies autolanding children are returned for a valid project"""
113 expected = ['autopilot-quantal-i386-autolanding',
114 'autopilot-quantal-amd64-autolanding']
115 actual = self.lookup.get_autolanding_name(self.stack, 'autopilot',
116 children=True)
117 self.assertEqual(expected, actual)
118
119 def test_get_autolanding_children_no_template(self):
120 """Verifies that no children are returned from a False template"""
121 expected = []
122 actual = self.lookup.get_autolanding_name(self.stack, 'autopilot-gtk',
123 children=True)
124 self.assertEqual(expected, actual)
125
126 def test_get_attribute_default_target_branch(self):
127 """Verifies the default target branch is returned"""
128 expected = 'lp:autopilot'
129 actual = self.lookup.get_attribute(self.stack, 'autopilot',
130 'autolanding', 'target_branch')
131 self.assertEqual(expected, actual)
132
133 def test_get_attribute_target_branch(self):
134 """Verifies the stack specified target branch is returned"""
135 expected = 'lp:xpathselect/feature'
136 actual = self.lookup.get_attribute(self.stack, 'xpathselect', 'ci',
137 'target_branch')
138 self.assertEqual(expected, actual)
139
140 def test_get_attribute_autolanding_value(self):
141 """Verifies the autolanding attribute value is returned"""
142 expected = {
143 'quantal-amd64': {
144 'node_label': 'pbuilder'},
145 'quantal-i386': {
146 'node_label': 'pbuilder'}}
147 actual = self.lookup.get_attribute(self.stack, 'autopilot',
148 'autolanding', 'configurations')
149 self.assertEqual(expected, actual)
150
151 def test_get_attribute_ci_value(self):
152 """Verifies the ci attribute value is returned"""
153 expected = {
154 'raring-amd64': {
155 'node_label': 'pbuilder'},
156 'raring-i386': {
157 'node_label': 'pbuilder'}}
158 actual = self.lookup.get_attribute(self.stack, 'autopilot',
159 'ci', 'configurations')
160 self.assertEqual(expected, actual)
161
162 def test_get_attribute_bad_project(self):
163 """Verifies that a missing project returns False"""
164 expected = False
165 actual = self.lookup.get_attribute(self.stack, 'bad_project',
166 'ci', 'bad_key')
167 self.assertEqual(expected, actual)
168
169 def test_get_attribute_bad_value(self):
170 """Verifies that a missing key raises a KeyError"""
171 with self.assertRaises(KeyError):
172 self.lookup.get_attribute(self.stack, 'autopilot', 'ci',
173 'bad_key')
174
175
176class Cu2dStackLookupCallTests(Cu2dStackLookupTestsBase):
177
178 def setUp(self):
179 super(Cu2dStackLookupCallTests, self).setUp()
180 self.args = type('Args', (object,), {'debug': False,
181 'base_path': '/tmp',
182 'list_projects': False,
183 'project': None,
184 'autoland': False,
185 'ci': False,
186 'attribute': None,
187 'children': False,
188 'stackcfg': 'stack.cfg'})
189 self.lookup.parse_arguments = lambda x: self.args
190 self.saved_stdout = sys.stdout
191 self.stdout = StringIO()
192 sys.stdout = self.stdout
193
194 def tearDown(self):
195 sys.stdout = self.saved_stdout
196
197 def test_bad_args(self):
198 """Verifies error return for invalid arguments"""
199 expected = 1
200 self.args = None
201 actual = self.lookup('/tmp')
202 self.assertEqual(expected, actual)
203
204 def test_no_stack(self):
205 """Verifies error return for an invalid stack file"""
206 expected = 1
207 self.args.list_projects = True
208 with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
209 new=lambda x: True), \
210 patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
211 new=lambda x, y: {}):
212 actual = self.lookup('/tmp')
213 self.assertEqual(expected, actual)
214
215 def test_no_projects(self):
216 """Verifies error return for a stack file with no projects"""
217 expected = 1
218 self.args.list_projects = True
219 with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
220 new=lambda x: True), \
221 patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
222 new=lambda x, y: {'ci_default': {}}):
223 actual = self.lookup('/tmp')
224 self.assertEqual(expected, actual)
225
226 def test_get_projects(self):
227 """Verifies output for a project list request"""
228 expected = 0
229 self.args.list_projects = True
230 with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
231 new=lambda x: True), \
232 patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
233 new=lambda x, y: self.stack):
234 actual = self.lookup('/tmp')
235 self.assertEqual(expected, actual)
236 output = '["autopilot", "autopilot-gtk", "autopilot-qt", ' \
237 '"xpathselect"]'
238 self.assertEqual(output, self.stdout.getvalue().strip())
239
240 def test_get_projects_none_found(self):
241 """Verifies error return when stack file contains no projects"""
242 expected = 1
243 self.args.list_projects = True
244 with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
245 new=lambda x: True), \
246 patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
247 new=lambda x, y: {'projects': {}}):
248 actual = self.lookup('/tmp')
249 self.assertEqual(expected, actual)
250
251 def test_get_attribute_ci(self):
252 """Verifies output for an attribute request"""
253 expected = 0
254 self.args.attribute = 'target_branch'
255 self.args.ci = True
256 self.args.project = 'xpathselect'
257 with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
258 new=lambda x: True), \
259 patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
260 new=lambda x, y: self.stack):
261 actual = self.lookup('/tmp')
262 self.assertEqual(expected, actual)
263 output = '"lp:xpathselect/feature"'
264 self.assertEqual(output, self.stdout.getvalue().strip())
265
266 def test_get_attribute_autoland(self):
267 """Verifies out for an attribute request for an autolanding job"""
268 expected = 0
269 self.args.attribute = 'target_branch'
270 self.args.project = 'xpathselect'
271 with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
272 new=lambda x: True), \
273 patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
274 new=lambda x, y: self.stack):
275 actual = self.lookup('/tmp')
276 self.assertEqual(expected, actual)
277 output = '"lp:xpathselect/feature"'
278 self.assertEqual(output, self.stdout.getvalue().strip())
279
280 def test_get_attribute_no_project(self):
281 """Verifies error return when the project is not found"""
282 expected = 1
283 self.args.attribute = 'target_branch'
284 self.args.project = 'not-found'
285 with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
286 new=lambda x: True), \
287 patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
288 new=lambda x, y: self.stack):
289 actual = self.lookup('/tmp')
290 self.assertEqual(expected, actual)
291
292 def test_get_ci_name(self):
293 """Verifies output for a ci job list request"""
294 expected = 0
295 self.args.ci = True
296 self.args.project = 'autopilot'
297 with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
298 new=lambda x: True), \
299 patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
300 new=lambda x, y: self.stack):
301 actual = self.lookup('/tmp')
302 self.assertEqual(expected, actual)
303 output = '["autopilot-ci"]'
304 self.assertEqual(output, self.stdout.getvalue().strip())
305
306 def test_get_ci_name_no_project(self):
307 """Verifies error return when the project is not found"""
308 expected = 1
309 self.args.ci = True
310 self.args.project = 'not-found'
311 with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
312 new=lambda x: True), \
313 patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
314 new=lambda x, y: self.stack):
315 actual = self.lookup('/tmp')
316 self.assertEqual(expected, actual)
317
318 def test_get_autolanding_name(self):
319 """Verifies output for an autolanding job list request"""
320 expected = 0
321 self.args.autoland = True
322 self.args.project = 'autopilot'
323 with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
324 new=lambda x: True), \
325 patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
326 new=lambda x, y: self.stack):
327 actual = self.lookup('/tmp')
328 self.assertEqual(expected, actual)
329 output = '["autopilot-autolanding"]'
330 self.assertEqual(output, self.stdout.getvalue().strip())
331
332 def test_get_autolanding_name_no_project(self):
333 """Verifies error return when the project is not found"""
334 expected = 1
335 self.args.autoland = True
336 self.args.project = 'not-found'
337 with patch('c2dconfigutils.cu2dStackLookup.load_default_cfg',
338 new=lambda x: True), \
339 patch('c2dconfigutils.cu2dStackLookup.load_stack_cfg',
340 new=lambda x, y: self.stack):
341 actual = self.lookup('/tmp')
342 self.assertEqual(expected, actual)
343
344
345class TestParseArguments(TestCase):
346 def setUp(self):
347 self.error_mock = MagicMock()
348
349 def test_gibberish(self):
350 """Verifies invalid arguments returns an error"""
351 sys_argv = ['./command', '-oheck']
352 with patch('sys.argv', sys_argv):
353 with patch('argparse.ArgumentParser.error', self.error_mock):
354 lookup = StackLookup()
355 lookup.parse_arguments('')
356 self.error_mock.assert_any_call(
357 'too few arguments')
358
359 def test_noparams(self):
360 """Verifies no arguments returns a too few arguments error"""
361 sys_argv = ['./command']
362 with patch('sys.argv', sys_argv):
363 with patch('argparse.ArgumentParser.error', self.error_mock):
364 lookup = StackLookup()
365 lookup.parse_arguments('')
366 self.error_mock.assert_any_call(
367 'too few arguments')
368
369 def test_missing_main_option(self):
370 """Verify error is returned for a missing primary option"""
371 sys_argv = ['./command', 'stack.cfg']
372 with patch('sys.argv', sys_argv):
373 with patch('argparse.ArgumentParser.error', self.error_mock):
374 lookup = StackLookup()
375 lookup.parse_arguments('')
376 self.error_mock.assert_called_with(
377 'Must specify one of -l/--list-projects or -p/--project')
378
379 def test_missing_project_option(self):
380 """Verify error is returned for a missing project option"""
381 sys_argv = ['./command', '--project', 'foo', 'stack.cfg']
382 with patch('sys.argv', sys_argv):
383 with patch('argparse.ArgumentParser.error', self.error_mock):
384 lookup = StackLookup()
385 lookup.parse_arguments('')
386 self.error_mock.assert_called_with(
387 'Must specify -a/--autolanding, -c/--ci or '
388 '-t/--attribute with -p/--project')
389
390 def test_missing_project_option_with_children(self):
391 """Verify error is returned for a missing project and children
392 option"""
393 sys_argv = ['./command', '--project', 'foo', '--attribute',
394 'target_branch', '--children', 'stack.cfg']
395 with patch('sys.argv', sys_argv):
396 with patch('argparse.ArgumentParser.error', self.error_mock):
397 lookup = StackLookup()
398 lookup.parse_arguments('')
399 self.error_mock.assert_called_with(
400 'Must specify -a/--autolanding or -c/--ci with '
401 '-i/--children')
402
403 def test_list_projects(self):
404 """Verify a list projects request"""
405 sys_argv = ['./command', '-l', 'stack.cfg']
406 with patch('sys.argv', sys_argv):
407 lookup = StackLookup()
408 ret = lookup.parse_arguments('')
409 self.assertTrue(ret.list_projects)
410
411 def test_project_ci(self):
412 """Verify a ci job request"""
413 sys_argv = ['./command', '-p', 'project', '-c', 'stack.cfg']
414 with patch('sys.argv', sys_argv):
415 lookup = StackLookup()
416 ret = lookup.parse_arguments('')
417 self.assertTrue(ret.ci)
418 self.assertEqual('project', ret.project)
419
420 def test_project_autoland(self):
421 """Verify a ci job request"""
422 sys_argv = ['./command', '-p', 'project', '-a', 'stack.cfg']
423 with patch('sys.argv', sys_argv):
424 lookup = StackLookup()
425 ret = lookup.parse_arguments('')
426 self.assertTrue(ret.autoland)
427 self.assertEqual('project', ret.project)
428
429 def test_project_ci_children(self):
430 """Verify a ci job request"""
431 sys_argv = ['./command', '-p', 'project', '-c', '-i', 'stack.cfg']
432 with patch('sys.argv', sys_argv):
433 lookup = StackLookup()
434 ret = lookup.parse_arguments('')
435 self.assertTrue(ret.ci)
436 self.assertTrue(ret.children)
437
438 def test_project_autoland_children(self):
439 """Verify a ci job request"""
440 sys_argv = ['./command', '-p', 'project', '-a', '-i', 'stack.cfg']
441 with patch('sys.argv', sys_argv):
442 lookup = StackLookup()
443 ret = lookup.parse_arguments('')
444 self.assertTrue(ret.autoland)
445 self.assertTrue(ret.children)
446
447 def test_project_attribute(self):
448 """Verify a ci job request"""
449 sys_argv = ['./command', '-p', 'project', '-t', 'target_branch',
450 'stack.cfg']
451 with patch('sys.argv', sys_argv):
452 lookup = StackLookup()
453 ret = lookup.parse_arguments('')
454 self.assertEqual('project', ret.project)
455 self.assertEqual('target_branch', ret.attribute)

Subscribers

People subscribed via source and target branches

to all changes: