Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue3549 adding test output to deploy task #3564

Open
wants to merge 70 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
11d5d7b
Issue3549 adding test output to deploy task
jtruit Mar 16, 2023
d7bacad
Merge branch 'main' into Issue3549
jstvz Apr 4, 2023
082f63c
Issue3549 Merge branch 'main' into Issue3549
jtruit Jun 6, 2023
c0461e7
Issue3549 removing left behind commented out line
jtruit Jun 6, 2023
e3362e6
Merge branch 'main' into Issue3549
jtruit Jul 17, 2023
0615395
Merge branch 'main' into Issue3549
jtruit Jul 18, 2023
bae0460
Merge branch 'main' into Issue3549
davidmreed Jul 31, 2023
a881bc7
Merge branch 'main' into Issue3549
jtruit Aug 8, 2023
df5d013
Merge branch 'main' into Issue3549
jtruit Aug 14, 2023
746ca24
Issue3549 Adding test to validate contents of file
jtruit Aug 28, 2023
ba87ba4
Merge branch 'main' into Issue3549
jtruit Sep 1, 2023
d54e268
Merge branch 'main' into Issue3549
jtruit Sep 13, 2023
ecf97a0
Merge branch 'main' into Issue3549
davidmreed Sep 19, 2023
936616e
Merge branch 'main' into Issue3549
davidmreed Sep 19, 2023
76e41fa
Issue3549 Merge branch 'main' into Issue3549 and updating tests
jtruit Sep 25, 2023
5ad1dc2
Issue3549 adding new parameter
jtruit Sep 26, 2023
3e8fb59
Merge branch 'main' into Issue3549
jtruit Sep 28, 2023
fe4fa9f
Merge branch 'main' into Issue3549
jtruit Sep 29, 2023
7839bbe
Merge branch 'main' into Issue3549
jtruit Oct 2, 2023
6b31085
Merge branch 'main' into Issue3549
jtruit Oct 11, 2023
fd27787
Merge branch 'main' into Issue3549
jtruit Oct 23, 2023
469d1d3
Merge branch 'main' into Issue3549
jtruit Oct 26, 2023
4581652
Merge branch 'main' into Issue3549
jtruit Nov 6, 2023
0fb61e7
Merge branch 'main' into Issue3549
jtruit Nov 8, 2023
4de86fb
Merge branch 'main' into Issue3549
jtruit Nov 17, 2023
5704b22
Merge branch 'main' into Issue3549
jtruit Dec 4, 2023
7b7a396
Merge branch 'main' into Issue3549
jtruit Dec 6, 2023
a24aff9
Merge branch 'main' into Issue3549
jtruit Dec 8, 2023
b755528
Merge branch 'main' into Issue3549
jtruit Dec 14, 2023
5437cbf
Merge branch 'main' into Issue3549
jtruit Dec 20, 2023
381a98d
Merge branch 'main' into Issue3549
jtruit Jan 1, 2024
0ba9316
Merge branch 'main' into Issue3549
jtruit Jan 24, 2024
5565b66
Merge branch 'main' into Issue3549
jtruit Jan 29, 2024
034509d
Merge branch 'main' into Issue3549
jtruit Feb 2, 2024
aff4cb5
Merge branch 'main' into Issue3549
jtruit Feb 5, 2024
3587681
Merge branch 'main' into Issue3549
jtruit Feb 7, 2024
eda22ee
Merge branch 'main' into Issue3549
jtruit Feb 7, 2024
83fe6bc
Merge branch 'main' into Issue3549
jtruit Feb 20, 2024
35fc7c5
Merge branch 'main' into Issue3549
jtruit Feb 26, 2024
eb9f910
Merge branch 'main' into Issue3549
jtruit Mar 7, 2024
2b0ee9c
Merge branch 'main' into Issue3549
jtruit Mar 18, 2024
17b89e3
Merge branch 'main' into Issue3549
jtruit Mar 28, 2024
fd2311f
Merge branch 'main' into Issue3549
jtruit May 8, 2024
55fe2b9
Merge branch 'main' into Issue3549
jtruit May 15, 2024
e30f4ac
Merge branch 'main' into Issue3549
jtruit May 20, 2024
88214e2
Merge branch 'main' into Issue3549
jtruit May 20, 2024
e1c78f0
Merge branch 'main' into Issue3549
jtruit May 24, 2024
5c1b9a9
Merge branch 'main' into Issue3549
jtruit May 29, 2024
33fced3
Merge branch 'main' into Issue3549
jtruit Jun 7, 2024
7caa1b6
Merge branch 'main' into Issue3549
jtruit Jun 13, 2024
ef941aa
Merge branch 'main' into Issue3549
jtruit Jun 18, 2024
aeb488b
Merge branch 'main' into Issue3549
jtruit Jun 21, 2024
6c4643a
Merge branch 'main' into Issue3549
jtruit Jul 2, 2024
1687309
Merge branch 'main' into Issue3549
jtruit Jul 16, 2024
e4d2715
Merge branch 'main' into Issue3549
jtruit Aug 23, 2024
e76342f
Merge branch 'main' into Issue3549
jtruit Sep 12, 2024
f146be5
Merge branch 'main' into Issue3549
jtruit Sep 18, 2024
2c62d4d
Merge branch 'main' into Issue3549
jtruit Sep 23, 2024
1dec662
Merge branch 'main' into Issue3549
jtruit Sep 25, 2024
b382245
Merge branch 'main' into Issue3549
jtruit Oct 10, 2024
7b4b732
Merge branch 'main' into Issue3549
jtruit Oct 14, 2024
260fa11
Merge branch 'main' into Issue3549
jtruit Oct 29, 2024
9ed29dc
Merge branch 'main' into Issue3549
jtruit Nov 19, 2024
920f08d
Merge branch 'main' into Issue3549
jtruit Nov 25, 2024
59ae317
Merge branch 'main' into Issue3549
jtruit Dec 3, 2024
9cb3c6b
Merge branch 'main' into Issue3549
jtruit Dec 10, 2024
e193684
Merge branch 'main' into Issue3549
jtruit Dec 17, 2024
1093136
Merge branch 'main' into Issue3549
jtruit Dec 20, 2024
ff865af
Merge branch 'main' into Issue3549
jtruit Jan 7, 2025
3979691
Merge branch 'main' into Issue3549
jtruit Jan 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ For example:
* Gustavo Tandeciarz (dcinzona)
* Chandler Anderson (zenibako)
* Ben French (BenjaminFrench)
* John Truitt (jtruit)
84 changes: 82 additions & 2 deletions cumulusci/salesforce_api/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
MetadataParseError,
)
from cumulusci.utils import parse_api_datetime, zip_subfolder
from cumulusci.tasks.apex.testrunner import RunApexTests

# If pyOpenSSL is installed, make sure it's not used for requests
# (it's not needed in the verisons of Python we support)
Expand Down Expand Up @@ -376,6 +377,7 @@ def __init__(
self,
task,
package_zip,
options={},
purge_on_delete=None,
api_version=None,
check_only=False,
Expand All @@ -390,7 +392,13 @@ def __init__(
self.check_only = "true" if check_only else "false"
self.test_level = test_level
self.package_zip = package_zip
self.run_tests = run_tests or []
self.options = options
# Set default values if nothing is passed
if self.options.get("junit_output") is None:
self.options["junit_output"] = "test_results.xml"
if self.options.get("json_output") is None:
self.options["json_output"] = "test_results.json"
self.run_tests = run_tests or []

def _set_purge_on_delete(self, purge_on_delete):
if not purge_on_delete or purge_on_delete == "false":
Expand Down Expand Up @@ -421,10 +429,73 @@ def _build_envelope_start(self):
run_tests=run_tests,
api_version=self.api_version,
)

def _parse_elements(self, file):
# Create Array for Test Results
parsed_results = []

# Parse out test results to add to log
successElements = file.getElementsByTagName("successes")
failElements = file.getElementsByTagName("failures")

for success in successElements:
# Get needed values from subelements
methodName = self._get_element_value(success, "methodName")
className = self._get_element_value(success, "name")
stats = {"duration": self._get_element_value(success, "time")}

parsed_results.append(
{
"Children": None,
"ClassName": className,
"Method": methodName,
"Message": None,
"Outcome": None,
"StackTrace": None,
"Stats": stats,
"TestTimestamp": None,
}
)

for failures in failElements:
# Get needed values from subelements
methodName = self._get_element_value(failures, "methodName")
className = self._get_element_value(failures, "name")
stats = {"duration": self._get_element_value(failures, "time")}
# Parse out any failure text (from test failures in production
# deployments) and add to log
namespace = self._get_element_value(failures, "namespace")
stacktrace = self._get_element_value(failures, "stackTrace")
testMessage = self._get_element_value(failures, "message")

message = ["Apex Test Failure: "]
if namespace:
message.append(f"from namespace {namespace}: ")
if stacktrace:
message.append(stacktrace)

parsed_results.append(
{
"Children": None,
"ClassName": className,
"Method": methodName,
"Message": testMessage,
"Outcome": "Fail",
"StackTrace": stacktrace,
"Stats": stats,
"TestTimestamp": None,
}
)

return parsed_results

def _process_response(self, response):
resp_xml = parseString(response.content)
status = resp_xml.getElementsByTagName("status")

# Create Array for Apex Test Results
test_results = []

if status:
status = status[0].firstChild.nodeValue
else:
Expand All @@ -436,6 +507,11 @@ def _process_response(self, response):
# related to done
if status in ["Succeeded", "SucceededPartial"]:
self._set_status("Success", status)

# Use existing function for apex tests to format and write output
test_results = self._parse_elements(resp_xml)
RunApexTests._write_output(self,test_results)

else:
# If failed, parse out the problem text and raise appropriate exception
messages = []
Expand Down Expand Up @@ -531,7 +607,11 @@ def _process_response(self, response):
message.append(f"from namespace {namespace}: ")
if stacktrace:
message.append(stacktrace)
messages.append("".join(message))
messages.append("".join(message))

# Use existing function for apex tests to format and write output
test_results = self._parse_elements(resp_xml)
RunApexTests._write_output(self,test_results)

if messages:
# Deploy failures due to a component failure should raise MetadataComponentFailure
Expand Down
40 changes: 40 additions & 0 deletions cumulusci/salesforce_api/tests/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
)
from cumulusci.tests.util import DummyOrgConfig, create_project_config

from cumulusci.utils import temporary_dir, touch

class DummyPackageZipBuilder(BasePackageZipBuilder):
def _populate_zip(self):
Expand Down Expand Up @@ -778,6 +779,45 @@ def test_process_response_failure_but_no_message(self):
api._process_response(response)
assert response.text == str(e.value)

def test_process_response_validate_test_output(self):
json_file = '.\\test_results.json'
xml_file = '.\\test_results.xml'
task = self._create_task()
api = self._create_instance(task)
response = Response()
response.status_code = 200
response.raw = io.BytesIO(
deploy_result_failure.format(
details="""<runTestResult>
<failures>
<namespace>test</namespace>
<stackTrace>stack</stackTrace>
</failures>
</runTestResult>
"""
).encode()
)

with temporary_dir():
with pytest.raises(ApexTestException):
api._process_response(response)

if touch(json_file):
expected = '"Outcome": "Fail"'

assert expected in json_file

else:
assert False

if touch(xml_file):
expected = '<failure type="failed" >'

assert expected in xml_file

else:
assert False

def test_get_action(self):
task = self._create_task()
api = self._create_instance(task)
Expand Down
16 changes: 16 additions & 0 deletions cumulusci/tasks/salesforce/Deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ class Deploy(BaseSalesforceMetadataApiTask):
"description": "Apply source transforms before deploying. See the CumulusCI documentation for details on how to specify transforms."
},
"rest_deploy": {"description": "If True, deploy metadata using REST API"},
"junit_output": {
"description": "XML test result output filename. Defaults to test_results.xml."
},
"json_output": {
"description": "JSON test result output filename. Defaults to test_results.json."
},
}

namespaces = {"sf": "http://soap.sforce.com/2006/04/metadata"}
Expand All @@ -74,6 +80,12 @@ class Deploy(BaseSalesforceMetadataApiTask):
def _init_options(self, kwargs):
super(Deploy, self)._init_options(kwargs)

# Set default values if nothing is passed
if self.options.get("junit_output") is None:
self.options["junit_output"] = "test_results.xml"
if self.options.get("json_output") is None:
self.options["json_output"] = "test_results.json"

self.check_only = process_bool_arg(self.options.get("check_only", False))
self.test_level = self.options.get("test_level")
if self.test_level and self.test_level not in [
Expand Down Expand Up @@ -139,6 +151,9 @@ def _get_api(self, path=None):
else:
self.logger.warning("Deployment package is empty; skipping deployment.")
return

options = {"junit_output": self.options["junit_output"],
"json_output": self.options["json_output"]}

# If rest_deploy param is set, update api_class to be RestDeploy
if self.rest_deploy:
Expand All @@ -147,6 +162,7 @@ def _get_api(self, path=None):
return self.api_class(
self,
package_zip,
options,
purge_on_delete=False,
check_only=self.check_only,
test_level=self.test_level,
Expand Down
39 changes: 39 additions & 0 deletions cumulusci/tasks/salesforce/tests/test_Deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,45 @@ def test_init_options__bad_transforms(self, rest_deploy):
)

assert "transform spec is not valid" in str(e)

@pytest.mark.parametrize("rest_deploy", [True, False])
def test_init_options__output_default(self, rest_deploy):
d = create_task(
Deploy,
{
"path": "src",
"rest_deploy": rest_deploy,
},
)

assert d.options["junit_output"] == "test_results.xml"
assert d.options["json_output"] == "test_results.json"

@pytest.mark.parametrize("rest_deploy", [True, False])
def test_init_options__JUNIT_output(self, rest_deploy):
d = create_task(
Deploy,
{
"path": "src",
"junit_output": "TEST.xml",
"rest_deploy": rest_deploy,
},
)

assert d.options["junit_output"] == "TEST.xml"

@pytest.mark.parametrize("rest_deploy", [True, False])
def test_init_options__JSON_output(self, rest_deploy):
d = create_task(
Deploy,
{
"path": "src",
"json_output": "TEST.json",
"rest_deploy": rest_deploy,
},
)

assert d.options["json_output"] == "TEST.json"

@pytest.mark.parametrize("rest_deploy", [True, False])
def test_freeze_sets_kind(self, rest_deploy):
Expand Down
2 changes: 2 additions & 0 deletions cumulusci/tasks/salesforce/tests/test_DeployBundles.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ def test_freeze(self):
"subfolder": "unpackaged/test",
"collision_check": False,
"namespace_inject": None,
'junit_output': 'test_results.xml',
'json_output': 'test_results.json',
}
]
},
Expand Down