def test_resume_session(self): # All of the tests below are using one session. The session has four # jobs, Job A depends on a resource provided by job R which has no # dependencies at all. Both Job X and Y depend on job A. # # A -(resource dependency)-> R # # X -(direct dependency) -> A # # Y -(direct dependency) -> A self.job_A = make_job("A", requires="R.attr == 'value'") self.job_A_expr = self.job_A.get_resource_program().expression_list[0] self.job_R = make_job("R", plugin="resource") self.job_X = make_job("X", depends='A') self.job_Y = make_job("Y", depends='A') self.job_list = [self.job_A, self.job_R, self.job_X, self.job_Y] # Create a new session (session_dir is empty) self.session = SessionState(self.job_list) result_R = JobResult({ 'job': self.job_R, 'io_log': make_io_log(((0, 'stdout', b"attr: value\n"), ), self._sandbox) }) result_A = JobResult({ 'job': self.job_A, 'outcome': JobResult.OUTCOME_PASS }) result_X = JobResult({ 'job': self.job_X, 'outcome': JobResult.OUTCOME_PASS }) # Job Y can't start as it requires job A self.assertFalse(self.job_state('Y').can_start()) self.session.update_desired_job_list([self.job_X, self.job_Y]) self.session.open() self.session.update_job_result(self.job_R, result_R) self.session.update_job_result(self.job_A, result_A) self.session.update_job_result(self.job_X, result_X) self.session.persistent_save() self.session.close() # Create a new session (session_dir should contain session data) self.session = SessionState(self.job_list) self.session.open() # Resume the previous session self.session.resume() # This time job Y can start self.assertTrue(self.job_state('Y').can_start()) self.session.close()
def test_init_with_identical_jobs(self): A = make_job("A") second_A = make_job("A") third_A = make_job("A") session = SessionState([A, second_A, third_A]) # But we don't really store both, just the first one self.assertEqual(session.job_list, [A])
def test_crash_on_missing_job(self): """ http://pad.lv/1334296 """ A = make_job("A") state = SessionState([]) problems = state.update_desired_job_list([A]) self.assertEqual(problems, [DependencyUnknownError(A)]) self.assertEqual(state.desired_job_list, [])
def make_realistic_test_session(self, session_dir): # Create a more realistic session with two jobs but with richer set # of data in the actual jobs and results. job_a = JobDefinition({ 'plugin': 'shell', 'name': 'job_a', 'summary': 'This is job A', 'command': 'echo testing && true', 'requires': 'job_b.ready == "yes"' }) job_b = JobDefinition({ 'plugin': 'resource', 'name': 'job_b', 'summary': 'This is job B', 'command': 'echo ready: yes' }) session = SessionState([job_a, job_b]) session.update_desired_job_list([job_a, job_b]) result_a = MemoryJobResult({ 'outcome': IJobResult.OUTCOME_PASS, 'return_code': 0, 'io_log': [(0, 'stdout', b'testing\n')], }) result_b = MemoryJobResult({ 'outcome': IJobResult.OUTCOME_PASS, 'return_code': 0, 'comments': 'foo', 'io_log': [(0, 'stdout', b'ready: yes\n')], }) session.update_job_result(job_a, result_a) session.update_job_result(job_b, result_b) return session
def test_mandatory_jobs_are_first_in_run_list(self): A = make_job('A') B = make_job('B') session = SessionState([A, B]) session.update_mandatory_job_list([B]) session.update_desired_job_list([A]) self.assertEqual(session.run_list, [B, A])
def test_also_after_suspend_manual_flag(self): # Define a job job = make_job("A", summary="foo", flags='also-after-suspend-manual') # Define an empty session session = SessionState([]) # Add the job to the session session.add_unit(job) # Both jobs got added to job list self.assertEqual(len(session.job_list), 2) self.assertIn(job, session.job_list) self.assertEqual(session.job_list[1].id, 'after-suspend-manual-A') self.assertEqual(session.job_list[1].summary, 'foo after suspend (S3)') self.assertEqual( session.job_list[1].depends, 'A com.canonical.certification::suspend/suspend_advanced') sibling = session.job_list[1] self.assertNotIn('also-after-suspend-manual', sibling.get_flag_set()) # Both jobs got added to job state map self.assertIs(session.job_state_map[job.id].job, job) self.assertIs(session.job_state_map[sibling.id].job, sibling) # Both jobs are not added to the desired job list self.assertNotIn(job, session.desired_job_list) self.assertNotIn(sibling, session.desired_job_list) # Both jobs are not in the run list self.assertNotIn(job, session.run_list) self.assertNotIn(sibling, session.run_list) # Both jobs are not selected to run self.assertEqual( session.job_state_map[job.id].readiness_inhibitor_list, [UndesiredJobReadinessInhibitor]) self.assertEqual( session.job_state_map[sibling.id].readiness_inhibitor_list, [UndesiredJobReadinessInhibitor])
def test_add_sibling_unit(self): # Define a job job = make_job("A", summary="foo", siblings='[{"id": "B"}]') # Define an empty session session = SessionState([]) # Add the job to the session session.add_unit(job) # Both jobs got added to job list self.assertEqual(len(session.job_list), 2) self.assertIn(job, session.job_list) self.assertEqual(session.job_list[1].id, 'B') self.assertEqual(session.job_list[1].summary, 'foo') sibling = session.job_list[1] # Both jobs got added to job state map self.assertIs(session.job_state_map[job.id].job, job) self.assertIs(session.job_state_map[sibling.id].job, sibling) # Both jobs are not added to the desired job list self.assertNotIn(job, session.desired_job_list) self.assertNotIn(sibling, session.desired_job_list) # Both jobs are not in the run list self.assertNotIn(job, session.run_list) self.assertNotIn(sibling, session.run_list) # Both jobs are not selected to run self.assertEqual( session.job_state_map[job.id].readiness_inhibitor_list, [UndesiredJobReadinessInhibitor]) self.assertEqual( session.job_state_map[sibling.id].readiness_inhibitor_list, [UndesiredJobReadinessInhibitor])
def test_category_map(self): """ Ensure that passing OPTION_WITH_CATEGORY_MAP causes a category id -> tr_name mapping to show up. """ exporter = self.TestSessionStateExporter([ SessionStateExporterBase.OPTION_WITH_CATEGORY_MAP ]) # Create three untis, two categories (foo, bar) and two jobs (froz, # bot) so that froz.category_id == foo cat_foo = CategoryUnit({ 'id': 'foo', 'name': 'The foo category', }) cat_bar = CategoryUnit({ 'id': 'bar', 'name': 'The bar category', }) job_froz = JobDefinition({ 'plugin': 'shell', 'id': 'froz', 'category_id': 'foo' }) # Create and export a session with the three units state = SessionState([cat_foo, cat_bar, job_froz]) session_manager = mock.Mock(spec_set=SessionManager, state=state) data = exporter.get_session_data_subset(session_manager) # Ensure that only the foo category was used, and the bar category was # discarded as nothing was referencing it self.assertEqual(data['category_map'], { 'foo': 'The foo category', })
def run(self): # Compute the run list, this can give us notification about problems in # the selected jobs. Currently we just display each problem # Create a session that handles most of the stuff needed to run jobs try: self.session = SessionState(self.job_list) except DependencyDuplicateError as exc: # Handle possible DependencyDuplicateError that can happen if # someone is using plainbox for job development. print("The job database you are currently using is broken") print("At least two jobs contend for the name {0}".format( exc.job.id)) print("First job defined in: {0}".format(exc.job.origin)) print("Second job defined in: {0}".format( exc.duplicate_job.origin)) raise SystemExit(exc) with self.session.open(): self._set_job_selection() self.runner = JobRunner(self.session.session_dir, self.provider_list, self.session.jobs_io_log_dir, command_io_delegate=self, dry_run=self.ns.dry_run) self._run_all_jobs() if self.config.fallback_file is not Unset: self._save_results() self._submit_results() # FIXME: sensible return value return 0
def __init__(self, provider, ns): super(AnalyzeInvocation, self).__init__(provider) self.ns = ns self.job_list = self.get_job_list(ns) self.desired_job_list = self._get_matching_job_list(ns, self.job_list) self.session = SessionState(self.job_list) self.problem_list = self.session.update_desired_job_list( self.desired_job_list)
def __init__(self, provider_list, config, ns): super().__init__(provider_list, config) self.ns = ns self.job_list = self.get_job_list(ns) self.desired_job_list = self._get_matching_job_list(ns, self.job_list) self.session = SessionState(self.job_list) self.problem_list = self.session.update_desired_job_list( self.desired_job_list)
def test_init_with_identical_jobs(self): A = make_job("A") second_A = make_job("A") third_A = make_job("A") # Identical jobs are folded for backwards compatibility with some local # jobs that re-added existing jobs session = SessionState([A, second_A, third_A]) # But we don't really store both, just the first one self.assertEqual(session.job_list, [A])
def __init__(self, provider_loader, config_loader, ns): super().__init__(provider_loader, config_loader) self.ns = ns self.unit_list = list( itertools.chain(*[p.unit_list for p in self.provider_list])) self.session = SessionState(self.unit_list) self.desired_job_list = self._get_matching_job_list( ns, self.session.job_list) self.problem_list = self.session.update_desired_job_list( self.desired_job_list)
def _add_test_plan_sheet(self, workbook, plan, job_list): """A sheet for a given test plan.""" # Create a sheet for this test plan sheet = workbook.add_worksheet(_('{}').format(plan.tr_name()[0:30])) # Define cell formatting fmt_header = workbook.add_format({ 'bold': True, 'font_color': '#ffffff', 'bg_color': '#77216f', # Light Aubergine }) fmt_code = workbook.add_format({ 'font_name': 'courier', }) fmt_info = workbook.add_format({ 'font_color': '#dd4814', # Ubuntu Orange 'font_name': 'Ubuntu', 'font_size': 16, }) # Create a section with static information sheet.write('A2', _("Test Plan Name"), fmt_info) sheet.write('B2', plan.tr_name()) sheet.write('A3', _("Test Plan ID"), fmt_info) sheet.write('B3', plan.id, fmt_code) sheet.merge_range('A4:B4', 'TIP: plainbox run -T {}'.format(plan.id), fmt_code) # We can add anything we want to all the rows in range(INFO_OFFSET) INFO_OFFSET = 5 # Find what is the effective run list of this test plan state = SessionState(job_list) state.update_desired_job_list( select_jobs(job_list, [plan.get_qualifier()])) def max_of(callback): """Get the maximum of some function applied to each job.""" return max((callback(job) for job in state.run_list), default=0) COL_ID, COL_SUMMARY = range(2) # Add columns: id sheet.write(INFO_OFFSET, COL_ID, _("Test Case ID"), fmt_header) sheet.write(INFO_OFFSET, COL_SUMMARY, _("Summary"), fmt_header) sheet.set_column( COL_ID, COL_ID, max_of(lambda job: nr_cols_of(job.partial_id)) * WIDTH_FACTOR + WIDTH_PADDING) sheet.set_column( COL_SUMMARY, COL_SUMMARY, max_of(lambda job: nr_cols_of(job.tr_summary())) * WIDTH_FACTOR + WIDTH_PADDING) # Add the information about each job as a separate row for index, job in enumerate(state.run_list, INFO_OFFSET + 1): sheet.set_row(index, HEIGHT_FACTOR + HEIGHT_PADDING) sheet.write(index, COL_ID, job.partial_id, fmt_code) sheet.write(index, COL_SUMMARY, job.tr_summary()) # Make sure the sheet is read only sheet.protect()
def test_init_with_colliding_jobs(self): # This is similar to the test above but the jobs actually differ In # this case the _second_ job is rejected but it really signifies a # deeper problem that should only occur during development of jobs A = make_job("A") different_A = make_job("A", plugin="resource") with self.assertRaises(DependencyDuplicateError) as call: SessionState([A, different_A]) self.assertIs(call.exception.job, A) self.assertIs(call.exception.duplicate_job, different_A) self.assertIs(call.exception.affected_job, different_A)
def test_get_estimated_duration_manual_unknown(self): four_seconds = make_job("four_seconds", plugin="shell", command="fibble", estimated_duration=4.0) no_estimated_duration = make_job("no_estimated_duration", plugin="user-verify", command="bibble") session = SessionState([four_seconds, no_estimated_duration]) session.update_desired_job_list([four_seconds, no_estimated_duration]) self.assertEqual(session.get_estimated_duration(), (4.0, None))
def make_test_session(self): # Create a small session with two jobs and two results job_a = make_job('job_a') job_b = make_job('job_b') session = SessionState([job_a, job_b]) session.update_desired_job_list([job_a, job_b]) result_a = make_job_result(outcome=IJobResult.OUTCOME_PASS) result_b = make_job_result(outcome=IJobResult.OUTCOME_FAIL) session.update_job_result(job_a, result_a) session.update_job_result(job_b, result_b) return session
def test_get_estimated_duration_automated_unknown(self): three_seconds = make_job("three_seconds", plugin="shell", command="frob", estimated_duration=3.0) no_estimated_duration = make_job("no_estimated_duration", plugin="shell", command="borf") session = SessionState([three_seconds, no_estimated_duration]) session.update_desired_job_list([three_seconds, no_estimated_duration]) self.assertEqual(session.get_estimated_duration(), (None, 0.0))
def test_crash_in_update_desired_job_list(self): # This checks if a DependencyError can cause crash # update_desired_job_list() with a ValueError, in certain conditions. A = make_job('A', depends='X') L = make_job('L', plugin='local') session = SessionState([A, L]) problems = session.update_desired_job_list([A, L]) # We should get exactly one DependencyMissingError related to job A and # the undefined job X (that is presumably defined by the local job L) self.assertEqual(len(problems), 1) self.assertIsInstance(problems[0], DependencyMissingError) self.assertIs(problems[0].affected_job, A)
def test_get_estimated_duration_manual(self): two_seconds = make_job("two_seconds", plugin="manual", command="farboo", estimated_duration=2.0) shell_job = make_job("shell_job", plugin="shell", command="boofar", estimated_duration=0.6) session = SessionState([two_seconds, shell_job]) session.update_desired_job_list([two_seconds, shell_job]) self.assertEqual(session.get_estimated_duration(), (0.6, 32.0))
def test_dont_remove_missing_jobs(self): """ http://pad.lv/1444126 """ A = make_job("A", depends="B") B = make_job("B", depends="C") state = SessionState([A, B]) problems = state.update_desired_job_list([A, B]) self.assertEqual(problems, [ DependencyMissingError(B, 'C', 'direct'), DependencyMissingError(A, 'B', 'direct'), ]) self.assertEqual(state.desired_job_list, []) self.assertEqual(state.run_list, [])
def test_get_estimated_duration_auto(self): # Define jobs with an estimated duration one_second = make_job("one_second", plugin="shell", command="foobar", estimated_duration=1.0) half_second = make_job("half_second", plugin="shell", command="barfoo", estimated_duration=0.5) session = SessionState([one_second, half_second]) session.update_desired_job_list([one_second, half_second]) self.assertEqual(session.get_estimated_duration(), (1.5, 0.0))
def setUp(self): self.A = make_job('a', name='A') self.B = make_job('b', name='B', plugin='local', description='foo') self.C = make_job('c', name='C') self.D = make_job('d', name='D', plugin='shell') self.E = make_job('e', name='E', plugin='shell') self.F = make_job('f', name='F', plugin='resource', description='baz') state = SessionState([self.A, self.B, self.C, self.D, self.E, self.F]) # D and E are a child of B state.job_state_map[self.D.id].via_job = self.B state.job_state_map[self.E.id].via_job = self.B self.tree = SelectableJobTreeNode.create_tree( state, [self.A, self.B, self.C, self.D, self.E, self.F])
def setUp(self): # All of the tests below are using one session. The session has four # jobs, clustered into two independent groups. Job A depends on a # resource provided by job R which has no dependencies at all. Job X # depends on job Y which in turn has no dependencies at all. # # A -(resource dependency)-> R # # X -(direct dependency) -> Y self.job_A = make_job("A", requires="R.attr == 'value'") self.job_A_expr = self.job_A.get_resource_program().expression_list[0] self.job_R = make_job("R", plugin="resource") self.job_X = make_job("X", depends='Y') self.job_Y = make_job("Y") self.job_list = [self.job_A, self.job_R, self.job_X, self.job_Y] self.session = SessionState(self.job_list)
def setUp(self): A = make_job('A') B = make_job('B', plugin='local', description='foo') C = make_job('C') D = make_job('D', plugin='shell') E = make_job('E', plugin='local', description='bar') F = make_job('F', plugin='shell') G = make_job('G', plugin='local', description='baz') R = make_job('R', plugin='resource') Z = make_job('Z', plugin='local', description='zaz') state = SessionState([A, B, C, D, E, F, G, R, Z]) # D and E are a child of B state.job_state_map[D.id].via_job = B state.job_state_map[E.id].via_job = B # F is a child of E state.job_state_map[F.id].via_job = E self.tree = JobTreeNode.create_tree(state, [R, B, C, D, E, F, G, A, Z])
def test_observe_result__missing_resource_key(self, mock_logger): job = make_job("R", plugin="resource") template = TemplateUnit({ 'template-resource': job.id, 'id': 'foo-{missing}', 'plugin': 'shell' }) result = mock.Mock(spec=IJobResult, outcome=IJobResult.OUTCOME_PASS) result.get_io_log.return_value = [(0, 'stdout', b'attr: value1\n'), (0, 'stdout', b'\n'), (0, 'stdout', b'attr: value2\n')] session_state = SessionState([template, job]) self.ctrl.observe_result(session_state, job, result) # Ensure that a warning was logged mock_logger.warning.assert_called_with( "Ignoring %s with missing template parameter %s", "foo-{missing}", "missing")
def test_set_resource_list(self): # Define an empty session session = SessionState([]) # Define a resource old_res = Resource({'attr': 'old value'}) # Set the resource list with the old resource # So here the old result is stored into a new 'R' resource session.set_resource_list('R', [old_res]) # Ensure that it worked self.assertEqual(session._resource_map, {'R': [old_res]}) # Define another resource new_res = Resource({'attr': 'new value'}) # Now we present the second result for the same job session.set_resource_list('R', [new_res]) # What should happen here is that the R resource is entirely replaced # by the data from the new result. The data should not be merged or # appended in any way. self.assertEqual(session._resource_map, {'R': [new_res]})
def test_add_job(self): # Define a job job = make_job("A") # Define an empty session session = SessionState([]) # Add the job to the session session.add_job(job) # The job got added to job list self.assertIn(job, session.job_list) # The job got added to job state map self.assertIs(session.job_state_map[job.id].job, job) # The job is not added to the desired job list self.assertNotIn(job, session.desired_job_list) # The job is not in the run list self.assertNotIn(job, session.run_list) # The job is not selected to run self.assertEqual( session.job_state_map[job.id].readiness_inhibitor_list, [UndesiredJobReadinessInhibitor])
def test_add_job_duplicate_job(self): # Define a job job = make_job("A") # Define an empty session session = SessionState([]) # Add the job to the session session.add_job(job) # The job got added to job list self.assertIn(job, session.job_list) # Define a perfectly identical job duplicate_job = make_job("A") self.assertEqual(job, duplicate_job) # Try adding it to the session # # Note that this does not raise any exceptions as the jobs are perfect # duplicates. session.add_job(duplicate_job) # The new job _did not_ get added to the job list self.assertEqual(len(session.job_list), 1) self.assertIsNot(duplicate_job, session.job_list[0])
def test_category_map_and_uncategorised(self): """ Ensure that OPTION_WITH_CATEGORY_MAP synthetizes the special 'uncategorised' category. """ exporter = self.TestSessionStateExporter([ SessionStateExporterBase.OPTION_WITH_CATEGORY_MAP ]) # Create a job without a specific category job = JobDefinition({ 'plugin': 'shell', 'id': 'id', }) # Create and export a session with that one job state = SessionState([job]) session_manager = mock.Mock(spec_set=SessionManager, state=state) data = exporter.get_session_data_subset(session_manager) # Ensure that the special 'uncategorized' category is used self.assertEqual(data['category_map'], { 'com.canonical.plainbox::uncategorised': 'Uncategorised', })