def test_failure_to_get_test_cycles(self, single_passing_xml, mocker): """Verify that API failure when retrieving test cycles is caught.""" # Mock mock_field_resp = mocker.Mock(spec=swagger_client.FieldResource) mock_field_resp.id = 12345 mock_field_resp.label = 'Failure Output' response = { 'items': [{ 'name': 'insert name here', 'id': 12345 }], 'total': 1 } mock_post_response = mocker.Mock(spec=requests.Response) mock_post_response.text = json.dumps(response) mocker.patch('requests.post', return_value=mock_post_response) mocker.patch('swagger_client.FieldApi.get_fields', return_value=[mock_field_resp]) mocker.patch('swagger_client.TestcycleApi.get_test_cycles', side_effect=ApiException('Super duper failure!')) # Setup test_cycle_name = 'TestCycle1' zz = ZigZag(single_passing_xml, TOKEN, PROJECT_ID, TEST_CYCLE) mhf = ModuleHierarchyFacade(zz) # Test with pytest.raises(RuntimeError): mhf.discover_root_test_cycle(test_cycle_name)
def test_tempest(self, mocker): """Validate when configured with tempest as tool""" # Mock zz = mocker.MagicMock() zz.test_runner = 'tempest' zz.utility_facade = UtilityFacade(zz) # Setup classname = 'tests.test_default' # Test mhf = ModuleHierarchyFacade(zz) assert ['tests.test_default'] == mhf.get_module_hierarchy(classname)
def test_create_test_cycle(self, single_passing_xml, mocker): """Verify that a new test cycle will be created when the desired cycle name cannot be found.""" # Setup test_cycle_name = 'Buttons' # Expectation test_cycle_pid_exp = 'CL-3' # Mock mock_field_resp = mocker.Mock(spec=swagger_client.FieldResource) mock_field_resp.id = 12345 mock_field_resp.label = 'Failure Output' mock_get_tc_resp = mocker.Mock(spec=swagger_client.TestCycleResource) mock_create_tc_resp = mocker.Mock( spec=swagger_client.TestCycleResource) mock_get_tc_resp.to_dict.return_value = { 'name': 'queens', 'pid': 'CL-2' } mock_create_tc_resp.to_dict.return_value = { 'name': test_cycle_name, 'pid': test_cycle_pid_exp } response = { 'items': [{ 'name': 'insert name here', 'id': 12345 }], 'total': 1 } mock_post_response = mocker.Mock(spec=requests.Response) mock_post_response.text = json.dumps(response) mocker.patch('requests.post', return_value=mock_post_response) mocker.patch('swagger_client.FieldApi.get_fields', return_value=[mock_field_resp]) mocker.patch('swagger_client.TestcycleApi.get_test_cycles', return_value=[mock_get_tc_resp]) mocker.patch('swagger_client.TestcycleApi.create_cycle', return_value=mock_create_tc_resp) # Setup zz = ZigZag(single_passing_xml, TOKEN, PROJECT_ID, TEST_CYCLE) mhf = ModuleHierarchyFacade(zz) # Test assert test_cycle_pid_exp == mhf.discover_root_test_cycle( test_cycle_name)
def test_bad_value(self, mocker): """Validate that if a bad value gets in we default to asc""" # Mock zz = mocker.MagicMock() zz.ci_environment = 'oops' zz.utility_facade = UtilityFacade(zz) zz.testsuite_props = ASC_TESTSUITE_PROPS # Setup classname = 'tests.test_default' # Test mhf = ModuleHierarchyFacade(zz) mh = mhf.get_module_hierarchy(classname) assert mh == ['foo', 'bar', 'baz', 'barf', 'test_default']
def test_asc(self, mocker): """Validate when configured with asc as ci-environment""" # Mock zz = mocker.MagicMock() zz.ci_environment = 'asc' zz.utility_facade = UtilityFacade(zz) zz.testsuite_props = ASC_TESTSUITE_PROPS # Setup classname = 'tests.test_default' # Test mhf = ModuleHierarchyFacade(zz) mh = mhf.get_module_hierarchy(classname) assert mh == ['foo', 'bar', 'baz', 'barf', 'test_default']
def test_mk8s_branch_periodic(self, mocker): """Validate when configured with mk8s as ci-environment""" # Mock zz = mocker.MagicMock() zz.ci_environment = 'mk8s' zz.utility_facade = UtilityFacade(zz) zz.testsuite_props = { 'BRANCH_NAME': 'master', } # Setup classname = 'tests.test_default' # Test mhf = ModuleHierarchyFacade(zz) assert mhf.get_module_hierarchy(classname) == ['tests.test_default']
def test_tempest_cycle(self, mocker): """Test the default test cycle for tempest tests""" # Setup zz = mocker.MagicMock() zz.test_runner = 'tempest' # Test assert 'Tempest' == ModuleHierarchyFacade(zz).get_test_cycle_name()
def __init__( self, junit_xml_file_path, qtest_api_token, qtest_project_id, qtest_test_cycle=None, # if None this will be assigned by the module_hierarchy_facade pprint_on_fail=False): """ Create a ZigZag facade class object. The ZigZag class uses the Facade pattern to call out to subsystems and sub Facades. Args: junit_xml_file_path (str): A file path to a XML element representing a JUnit style testsuite response. qtest_api_token (str): Token to use for authorization to the qTest API. qtest_project_id (int): The target qTest project for the test results. qtest_test_cycle (str): The parent qTest test cycle for test results. (e.g. Product Release codename "Queens") pprint_on_fail (bool): A flag for enabling debug pretty print on schema failure. """ swagger_client.configuration.api_key['Authorization'] = qtest_api_token self._qtest_api_token = qtest_api_token self._junit_xml_file_path = junit_xml_file_path self._qtest_project_id = qtest_project_id self._qtest_test_cycle_name = qtest_test_cycle self._pprint_on_fail = pprint_on_fail self._test_logs = [] # properties that will be written to an instance of this class as a mediator self._ci_environment = None self._build_number = None self._build_url = None self._testsuite_props = None self._serialized_junit_xml = None self._junit_xml = None self._junit_xml_doc = None self._qtest_test_cycle_pid = None self._utility_facade = UtilityFacade(self) self._parsing_facade = XmlParsingFacade(self) self._requirement_link_facade = RequirementsLinkFacade(self) self._module_hierarchy_facade = ModuleHierarchyFacade(self) self._test_runner = 'pytest-zigzag' # the default test_runner assumed by ZigZag
def test_mk8s_non_pr_cycle(self, mocker): """Verify that non PR tests use the branch name""" # Setup zz = mocker.MagicMock() zz.ci_environment = 'mk8s' branch = 'Master' zz.testsuite_props = {'BRANCH_NAME': branch} # Test assert branch == ModuleHierarchyFacade(zz).get_test_cycle_name()
def test_mk8s_pr_cycle(self, mocker): """Verify that mk8s pr test cycle gets set correctly""" # Setup zz = mocker.MagicMock() zz.ci_environment = 'mk8s' branch = 'PR-123' zz.testsuite_props = {'BRANCH_NAME': branch} # Test assert 'PR' == ModuleHierarchyFacade(zz).get_test_cycle_name()
def test_asc_cycle(self, mocker): """Verify the default test cycle for asc tests""" # Setup zz = mocker.MagicMock() zz.ci_environment = 'asc' release = 'Pike' zz.testsuite_props = {'RPC_PRODUCT_RELEASE': release} # Test assert release == ModuleHierarchyFacade(zz).get_test_cycle_name()
def test_discover_existing_test_cycle_with_case_change( self, single_passing_xml, mocker): """Verify that the PID for an existing test cycle can be discovered when using a different case for search.""" # Expectation test_cycle_pid_exp = 'CL-2' # Mock mock_field_resp = mocker.Mock(spec=swagger_client.FieldResource) mock_field_resp.id = 12345 mock_field_resp.label = 'Failure Output' mock_tc_resp = mocker.Mock(spec=swagger_client.TestCycleResource) mock_tc_resp.to_dict.return_value = { 'name': 'queens', 'pid': test_cycle_pid_exp } response = { 'items': [{ 'name': 'insert name here', 'id': 12345 }], 'total': 1 } mock_post_response = mocker.Mock(spec=requests.Response) mock_post_response.text = json.dumps(response) mocker.patch('requests.post', return_value=mock_post_response) mocker.patch('swagger_client.FieldApi.get_fields', return_value=[mock_field_resp]) mocker.patch('swagger_client.TestcycleApi.get_test_cycles', return_value=[mock_tc_resp]) # Setup test_cycle_name = 'Queens' zz = ZigZag(single_passing_xml, TOKEN, PROJECT_ID, TEST_CYCLE) mhf = ModuleHierarchyFacade(zz) # Test assert test_cycle_pid_exp == mhf.discover_root_test_cycle( test_cycle_name)
def __init__(self, junit_xml_file_path, config_file, qtest_api_token, pprint_on_fail=False): """ Create a ZigZag facade class object. The ZigZag class uses the Facade pattern to call out to subsystems and sub Facades. Args: junit_xml_file_path (str): A file path to a XML element representing a JUnit style testsuite response. config_file (str): A file path to a JSON config file qtest_api_token (str): Token to use for authorization to the qTest API. pprint_on_fail (bool): A flag for enabling debug pretty print on schema failure. """ swagger_client.configuration.api_key['Authorization'] = qtest_api_token self._qtest_api_token = qtest_api_token self._junit_xml_file_path = junit_xml_file_path self._pprint_on_fail = pprint_on_fail self._config_file = config_file self._test_logs = [] # properties that will be written to an instance of this class as a mediator self._qtest_test_cycle_name = None self._build_number = None self._build_url = None self._testsuite_props = None self._serialized_junit_xml = None self._junit_xml = None self._junit_xml_doc = None self._qtest_test_cycle_pid = None self._qtest_project_id = None self._config_dict = None self._utility_facade = UtilityFacade(self) self._parsing_facade = XmlParsingFacade(self) self._requirement_link_facade = RequirementsLinkFacade(self) self._module_hierarchy_facade = ModuleHierarchyFacade(self)
class ZigZag(object): def __init__( self, junit_xml_file_path, qtest_api_token, qtest_project_id, qtest_test_cycle=None, # if None this will be assigned by the module_hierarchy_facade pprint_on_fail=False): """ Create a ZigZag facade class object. The ZigZag class uses the Facade pattern to call out to subsystems and sub Facades. Args: junit_xml_file_path (str): A file path to a XML element representing a JUnit style testsuite response. qtest_api_token (str): Token to use for authorization to the qTest API. qtest_project_id (int): The target qTest project for the test results. qtest_test_cycle (str): The parent qTest test cycle for test results. (e.g. Product Release codename "Queens") pprint_on_fail (bool): A flag for enabling debug pretty print on schema failure. """ swagger_client.configuration.api_key['Authorization'] = qtest_api_token self._qtest_api_token = qtest_api_token self._junit_xml_file_path = junit_xml_file_path self._qtest_project_id = qtest_project_id self._qtest_test_cycle_name = qtest_test_cycle self._pprint_on_fail = pprint_on_fail self._test_logs = [] # properties that will be written to an instance of this class as a mediator self._ci_environment = None self._build_number = None self._build_url = None self._testsuite_props = None self._serialized_junit_xml = None self._junit_xml = None self._junit_xml_doc = None self._qtest_test_cycle_pid = None self._utility_facade = UtilityFacade(self) self._parsing_facade = XmlParsingFacade(self) self._requirement_link_facade = RequirementsLinkFacade(self) self._module_hierarchy_facade = ModuleHierarchyFacade(self) self._test_runner = 'pytest-zigzag' # the default test_runner assumed by ZigZag # properties with only getters @property def utility_facade(self): """Gets the attached utility facade. Returns: UtilityFacade """ return self._utility_facade @property def module_hierarchy_facade(self): """Gets the attached module_hierarchy_facade Returns: ModuleHierarchyFacade """ return self._module_hierarchy_facade @property def qtest_api_token(self): """Gets the qTest API token Returns: str: The qTest API token """ return self._qtest_api_token @property def junit_xml_file_path(self): """Gets the junit XML file path Returns: str: The file path for the junit xml file """ return self._junit_xml_file_path @property def qtest_project_id(self): """Gets the qTest project ID Returns: int: The qTest project ID """ return self._qtest_project_id @property def qtest_test_cycle_name(self): """Gets the qTest test cycle Returns: str: The qTest test cycle """ if self._qtest_test_cycle_name is None: self._qtest_test_cycle_name = self._module_hierarchy_facade.get_test_cycle_name( ) return self._qtest_test_cycle_name @property def qtest_test_cycle_pid(self): """Gets the PID for the qtest_test_cycle that zigzag will attempt to load to Returns: str: The PID of the test_cycle """ if self._qtest_test_cycle_pid is None: self._qtest_test_cycle_pid = self._module_hierarchy_facade.discover_root_test_cycle( self.qtest_test_cycle_name) return self._qtest_test_cycle_pid @property def pprint_on_fail(self): """Get the pprint value Returns: bool: If zigzag should pprint """ return self._pprint_on_fail # properties with setters and getters @property def test_runner(self): """Gets the test_runner used to generate the files to be processed Returns: str: the name of the test_runner used to generate the files to be processed """ return self._test_runner @test_runner.setter def test_runner(self, value): """Sets the value for test_runner""" self._test_runner = value @property def build_url(self): """Gets the build_url Returns: str: The url for the build """ return self._build_url @build_url.setter def build_url(self, value): """Sets the value for build_url """ self._build_url = value @property def build_number(self): """Gets the value for build_number Returns: int: the number of the build """ return self._build_number @build_number.setter def build_number(self, value): """Sets the number of the build """ self._build_number = value @property def testsuite_props(self): """Gets the value for testsuite_props Returns: dict: the properties of the testsuite """ return self._testsuite_props @testsuite_props.setter def testsuite_props(self, value): """Sets the properties of the testsuite""" self._testsuite_props = value @property def junit_xml(self): """Gets the junit xml Returns: ElementTree: The junit xml element tree """ return self._junit_xml @junit_xml.setter def junit_xml(self, value): """Sets the junit xml """ self._junit_xml = value @property def junit_xml_doc(self): """Gets the junit_xml_doc Returns: ElementTree: The junit xml element tree """ return self._junit_xml_doc @junit_xml_doc.setter def junit_xml_doc(self, value): """Sets the junit_xml_doc """ self._junit_xml_doc = value @property def serialized_junit_xml(self): """Gets the serialized junit xml Returns: str: The serialized junit xml """ return self._serialized_junit_xml @serialized_junit_xml.setter def serialized_junit_xml(self, value): """Sets the serialized junit xml""" self._serialized_junit_xml = value @property def ci_environment(self): """Gets the configured test_runner Returns: str: the configured ci_environment """ return self._ci_environment @ci_environment.setter def ci_environment(self, value): """Sets the configured ci_environment""" self._ci_environment = value @property def test_logs(self): """Gets the test log objects. Returns: zigzag.zigzag_test_log.ZigZagTestLogs: A sequence object containing of ZigZagTestLog objects """ return self._test_logs @test_logs.setter def test_logs(self, value): """Set the test log objects.""" self._test_logs = value def _generate_auto_request(self): """Construct a qTest swagger model for a JUnitXML test run result. (Called an "automation request" in qTest parlance) Returns: AutomationRequest: A qTest swagger model for an automation request. """ auto_req = swagger_client.AutomationRequest() auto_req.test_logs = [] for log in self.test_logs: try: auto_req.test_logs.append(log.qtest_test_log) except ZigZagTestLogError: pass # if we cant find automation content this is a bad record auto_req.test_cycle = self.qtest_test_cycle_pid auto_req.execution_date = datetime.utcnow().strftime( '%Y-%m-%dT%H:%M:%SZ') # UTC timezone 'Zulu' return auto_req def upload_test_results(self): """Construct a 'AutomationRequest' qTest resource and upload the test results to the desired project in qTest Manager. Returns: int: The queue processing ID for the job. Raises: RuntimeError: Failed to upload test results to qTest Manager. """ auto_api = swagger_client.TestlogApi() auto_req = self._generate_auto_request() try: response = auto_api.submit_automation_test_logs_0( project_id=self._qtest_project_id, body=auto_req, type='automation') except ApiException as e: raise RuntimeError("The qTest API reported an error!\n" "Status code: {}\n" "Reason: {}\n" "Message: {}".format(e.status, e.reason, e.body)) if response.state == 'FAILED': raise RuntimeError( "The qTest API failed to process the job!\nJob ID: {}".format( response.id)) # for now we will always try to link the cases # this should move somewhere else in the future or be optional to this self._requirement_link_facade.link() return int(response.id) def parse(self): """Parse the xml""" self._parsing_facade.parse() # this was moved from the init method