# Copyright (C) 2017 Lars Wirzenius # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import os import shutil import tempfile import unittest import ick2 class ControllerAPITests(unittest.TestCase): def setUp(self): self.tempdir = tempfile.mkdtemp() self.statedir = os.path.join(self.tempdir, 'state/dir') def tearDown(self): shutil.rmtree(self.tempdir) def create_api(self): api = ick2.ControllerAPI() api.set_state_directory(self.statedir) return api def test_has_no_state_directory_initially(self): api = ick2.ControllerAPI() statedir = api.get_state_directory() self.assertTrue(statedir is None) def test_sets_and_creates_state_directory(self): api = self.create_api() statedir = api.get_state_directory() self.assertEqual(statedir, self.statedir) self.assertTrue(os.path.exists(statedir)) class VersionAPITests(unittest.TestCase): def create_api(self): api = ick2.VersionAPI(None) return api def test_returns_version_correcly(self): api = ick2.VersionAPI(None) response = api.get_version() self.assertEqual( response, { 'version': ick2.__version__, } ) class ProjectAPITests(unittest.TestCase): def setUp(self): self.tempdir = tempfile.mkdtemp() self.statedir = os.path.join(self.tempdir, 'state/dir') self.state = ick2.ControllerState() self.state.set_state_directory(self.statedir) def tearDown(self): shutil.rmtree(self.tempdir) def create_api(self): return ick2.ProjectAPI(self.state) def test_has_not_projects_initially(self): api = self.create_api() self.assertEqual(api.list(), {'projects': []}) def test_creates_project(self): project = { 'project': 'foo', 'pipelines': [ { 'name': 'build', 'actions': [ { 'shell': 'step-1', }, ], }, ], } api = self.create_api() self.assertEqual(api.create(project), project) self.assertEqual(api.list(), {'projects': [project]}) self.assertEqual(api.get_pipeline('foo', 'build'), {'status': 'idle'}) self.assertEqual( api.get_builds('foo'), {'project': 'foo', 'builds': []} ) def test_raises_error_when_getting_missing_pipeline(self): project = { 'project': 'foo', 'pipelines': [ { 'name': 'build', 'actions': [ { 'shell': 'step-1', }, ], }, ], } api = self.create_api() api.create(project) with self.assertRaises(ick2.NotFound): api.get_pipeline('foo', 'does-not-exist') def test_loads_projects_from_state_directory(self): project = { 'project': 'foo', 'shell_steps': ['build'], } api = self.create_api() api.create(project) api2 = self.create_api() self.assertEqual(api2.list(), {'projects': [project]}) def test_gets_named_project(self): project = { 'project': 'foo', 'shell_steps': ['build'], } api = self.create_api() api.create(project) self.assertEqual(api.show('foo'), project) def test_updates_named_project(self): project_v1 = { 'project': 'foo', 'shell_steps': ['build'], } project_v2 = dict(project_v1) project_v2['shell_steps'] = ['build it using magic'] api = self.create_api() api.create(project_v1) updated = api.update(project_v2, 'foo') self.assertEqual(updated, project_v2) self.assertEqual(api.show('foo'), project_v2) def test_deletes_named_project(self): project = { 'project': 'foo', 'shell_steps': ['build'], } api = self.create_api() api.create(project) api.delete('foo') self.assertEqual(api.list(), {'projects': []}) with self.assertRaises(ick2.NotFound): api.show('foo') def test_raises_error_deleting_missing_project(self): api = self.create_api() with self.assertRaises(ick2.NotFound): api.delete('foo') def test_updates_pipeline_status(self): project = { 'project': 'foo', 'pipelines': [ { 'name': 'build', 'actions': [ { 'shell': 'step-1', }, ], }, ], } api = self.create_api() api.create(project) self.assertEqual(api.get_pipeline('foo', 'build'), {'status': 'idle'}) with self.assertRaises(ick2.WrongPipelineStatus): api.set_pipeline('building', 'foo', 'build') api.set_pipeline('triggered', 'foo', 'build') self.assertEqual( api.get_pipeline('foo', 'build'), {'status': 'triggered'} ) with self.assertRaises(ick2.WrongPipelineStatus): api.set_pipeline('idle', 'foo', 'build') api.set_pipeline('building', 'foo', 'build') self.assertEqual( api.get_pipeline('foo', 'build'), {'status': 'building'} ) with self.assertRaises(ick2.WrongPipelineStatus): api.set_pipeline('triggered', 'foo', 'build') api.set_pipeline('idle', 'foo', 'build') self.assertEqual( api.get_pipeline('foo', 'build'), {'status': 'idle'} ) def test_raises_error_updating_status_of_missing_pipeline(self): project = { 'project': 'foo', 'pipelines': [], } api = self.create_api() api.create(project) with self.assertRaises(ick2.NotFound): api.set_pipeline('idle', 'foo', 'build') class WorkAPITests(unittest.TestCase): def setUp(self): self.tempdir = tempfile.mkdtemp() self.statedir = os.path.join(self.tempdir, 'state/dir') self.state = ick2.ControllerState() self.state.set_state_directory(self.statedir) def tearDown(self): shutil.rmtree(self.tempdir) def create_project_api(self): project = { 'project': 'foo', 'pipelines': [ { 'name': 'build', 'actions': [ { 'shell': 'step-1', }, { 'shell': 'step-2', }, ], }, ], } api = ick2.ProjectAPI(self.state) api.create(project) return api def create_worker_api(self): worker = { 'worker': 'asterix', } api = ick2.WorkerAPI(self.state) api.create(worker) return api def create_work_api(self): return ick2.WorkAPI(self.state) def test_worker_gets_no_work_when_no_pipeline_is_triggered(self): self.create_project_api() self.create_worker_api() work = self.create_work_api() self.assertEqual(work.get_work('asterix'), {}) def test_worker_gets_work_when_a_pipeline_is_triggered(self): projects = self.create_project_api() projects.set_pipeline('triggered', 'foo', 'build') self.create_worker_api() work = self.create_work_api() expected = { 'build_id': 1, 'worker': 'asterix', 'project': 'foo', 'pipeline': 'build', 'step': { 'shell': 'step-1', }, 'step_index': 0, 'log': '/logs/1', } self.assertEqual(work.get_work('asterix'), expected) # Check we get the same thing twice. self.assertEqual(work.get_work('asterix'), expected) def test_worker_manager_posts_work_updates(self): projects = self.create_project_api() projects.set_pipeline('triggered', 'foo', 'build') self.create_worker_api() work = self.create_work_api() # Ask for some work. expected = { 'build_id': 1, 'worker': 'asterix', 'project': 'foo', 'pipeline': 'build', 'step': { 'shell': 'step-1', }, 'step_index': 0, 'log': '/logs/1', } self.assertEqual(work.get_work('asterix'), expected) # Post a partial update. done = { 'build_id': 1, 'worker': 'asterix', 'project': 'foo', 'pipeline': 'build', 'exit_code': None, 'stdout': 'out', 'stderr': 'err', 'timestamp': '2000-01-01T00:00:00', } work.update_work(done) # Ask for work again. We didn't finish the previous step, so # should get same thing. self.assertEqual(work.get_work('asterix'), expected) # Finish the step. done['exit_code'] = 0 work.update_work(done) # We should get the next step now. expected['step'] = {'shell': 'step-2'} expected['step_index'] = 1 self.assertEqual(work.get_work('asterix'), expected) # Finish the step. done['exit_code'] = 0 work.update_work(done) # We now get nothing further to do. self.assertEqual(work.get_work('asterix'), {}) # An pipeline status has changed. self.assertEqual( projects.get_pipeline('foo', 'build'), {'status': 'idle'}) def test_worker_manager_posts_failure(self): projects = self.create_project_api() projects.set_pipeline('triggered', 'foo', 'build') self.create_worker_api() work = self.create_work_api() # Ask for some work. expected = { 'build_id': 1, 'worker': 'asterix', 'project': 'foo', 'pipeline': 'build', 'step': { 'shell': 'step-1', }, 'step_index': 0, 'log': '/logs/1', } self.assertEqual(work.get_work('asterix'), expected) # Post a partial update. done = { 'build_id': 1, 'worker': 'asterix', 'project': 'foo', 'pipeline': 'build', 'exit_code': 1, 'stdout': 'out', 'stderr': 'err', 'timestamp': '2000-01-01T00:00:00', } work.update_work(done) # Ask for work again. self.assertEqual(work.get_work('asterix'), {}) # An pipeline status has changed. self.assertEqual( projects.get_pipeline('foo', 'build'), {'status': 'idle'})