-
Notifications
You must be signed in to change notification settings - Fork 273
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
Add test_recorder implementation and integration with integ_test_suite #467
Conversation
Signed-off-by: Sayali Gaikawad <gaiksaya@amazon.com>
Signed-off-by: Sayali Gaikawad <gaiksaya@amazon.com>
Signed-off-by: Sayali Gaikawad <gaiksaya@amazon.com>
Codecov Report
@@ Coverage Diff @@
## main #467 +/- ##
==========================================
- Coverage 64.46% 63.44% -1.02%
==========================================
Files 48 51 +3
Lines 1269 1488 +219
==========================================
+ Hits 818 944 +126
- Misses 451 544 +93
Continue to review full report at Codecov.
|
Signed-off-by: Sayali Gaikawad <gaiksaya@amazon.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly small stuff below.
There's a lot of passing 4-6 parameters around, consider some refactoring into configuration objects.
You should work off your own fork to avoid everyone bringing everybody's branches along.
@@ -73,11 +77,12 @@ def _is_security_enabled(self, config): | |||
return False | |||
|
|||
def _setup_cluster_and_execute_test_config(self, config): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the convention for private methods? I think we've been doing two __
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not written by me but I can change if @setiah is okay with that or not making changes on his side
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may skip this. This (and more) will be refactored in a separate PR which adds UTs as well
bundle-workflow/src/test_workflow/integ_test/integ_test_suite.py
Outdated
Show resolved
Hide resolved
bundle-workflow/src/test_workflow/integ_test/integ_test_suite.py
Outdated
Show resolved
Hide resolved
bundle-workflow/src/test_workflow/integ_test/local_test_cluster.py
Outdated
Show resolved
Hide resolved
else: | ||
return "FAILED/ERROR" | ||
|
||
def __generate_test_outcome_yml(self, component_name, component_test_config, exit_status, log_file_location): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
log_file_location -> output_path
Signed-off-by: Sayali Gaikawad <gaiksaya@amazon.com>
Signed-off-by: Sayali Gaikawad <gaiksaya@amazon.com>
@@ -37,6 +38,9 @@ def parse_arguments(): | |||
parser.add_argument( | |||
"--test-manifest", type=argparse.FileType("r"), help="Test Manifest file." | |||
) | |||
parser.add_argument( | |||
"--test-run-id", type=str, help="Test Run ID" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -104,6 +108,7 @@ def main(): | |||
bundle_manifest = BundleManifest.from_file(args.bundle_manifest) | |||
build_manifest = BuildManifest.from_file(args.build_manifest) | |||
test_manifest = TestManifest.from_file(args.test_manifest) | |||
test_recorder = TestRecorder(args.test_run_id, "integ-test") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to define a standard constants which defines each of the test suites.
We would want all the workflows to use same identifiers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that would a part of it. I was getting towards something in common which could be used by all workflows not something which TestRecorder can understand. But this is a good start.
import shutil | ||
import tempfile | ||
|
||
import yaml | ||
|
||
|
||
class TestRecorder: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this class a singleton. With the design pattern we have, we'd like to have just one instance of TestRecorder for a given testsuite/jenkins job.
This also prevents other instantiations, avoids passing the test recorder instance in multiple places within the code base.
class TestRecorder: | |
class TestRecorder: | |
__instance = None | |
@staticmethod | |
def getInstance(test_run_id, test_type, location=None): | |
""" Static access method. """ | |
if TestRecorder.__instance == None: | |
TestRecorder(test_run_id, test_type, location) | |
return TestRecorder.__instance |
Python by default doesn't have private constructors, if you'd like to block instantiation outside of the class, you could raise an error in __init__
and use a different private method which could be called by getInstance(...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also rename getInstance to get_instance
logging.info(f"Recording remote cluster logs for {component_name} in {os.path.realpath(dest_directory)}") | ||
self.__generate_std_files(stdout, stderr, os.path.realpath(dest_directory)) | ||
exit_status = self.__get_exit_status(exit_code) | ||
component_yml = self.__generate_test_outcome_yml(component_name, component_test_config, exit_status, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
component_yml = self.__generate_test_outcome_yml(component_name, component_test_config, exit_status, | |
test_outcome_yml = self.__generate_test_outcome_yml(component_name, component_test_config, exit_status, |
for log_file in local_cluster_log_files: | ||
dest_file = os.path.join(dest_directory, os.path.basename(log_file[0])) | ||
shutil.copyfile(log_file[0], dest_file) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this could be stubbed out into another method. This code is repeated in record_remote_cluster_logs
and record_test_outcome
dest_file = os.path.join(dest_directory, os.path.basename(log_file[0])) | ||
shutil.copyfile(log_file[0], dest_file) | ||
|
||
def record_remote_cluster_logs(self, component_name, component_test_config, exit_code, stdout, stderr, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The flow doesn't look clean to me. Let me know if I didn't understand this correctly.
- For test suite which uses local cluster, it uses both
record_test_outcome
andrecord_local_cluster_logs
.
record_test_outcome
generates the test outcome manifest. - For test suite which uses remote cluster, it just uses
record_remote_cluster_logs
.record_remote_cluster_logs
generates the test outcome manifest.
If that is the case why don't we merge both remote and local test cluster into one function record_test_cluster
which takes in local/remote as a parameter. The only logic different between them is creating a different directory.
Let me know what you think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. I'll work on this. One more logic that differs is remote cluster logs won't have log_files but just the location of the logs. Will see how to differentiate that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
record_local_cluster_logs
also copies the local test cluster log files into the directory where remote cluster just gives the location of the logs. That will be an additional change but with right param it should be distinguishable
if self.test_type not in self.ACCEPTED_TEST_TYPES: | ||
raise ValueError(f"test_type is invalid. Acceptable test_type are {self.ACCEPTED_TEST_TYPES}") | ||
|
||
if location is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to clean up the directory when TestRecorder goes out of scope. Tmp directory gives us a great feature which takes care of it. It's better to clean it up by ourself, we do not want to leave them on the system.
Lets only use temporary directory as the only option for TestRecorder and when it goes out of scope let it destroy the directory and add a keep parameter if caller wants the directory to be preserved.
Anyway we don't have a use case yet for a custom directory, avoids the hassle of cleaning up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Regarding clean up. We cannot clean up the directory unless publisher is done publishing the result. So would it make sense that publisher deletes the directory when it is done publishing? or is there any other way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets leave the ownership to the test suite runners. They use test recorder, TestRecorder records them and deletes them if it goes out of scope.
Any way TestPublisher(aka TestResults) consumes TestRecorder so it is implicitly taken care of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sarat suggests that the topmost caller creates a temporary dir and it's passed down to anyone that needs to write files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that is happening right now. Will see how to clean_up when the test_recorder object goes out of scope
Closing this one. Please see: #587 |
Signed-off-by: Sayali Gaikawad gaiksaya@amazon.com
Description
Adds test_recorder implementation and integration with integ_test_suite.
Test_recorder will be instantiated by each test_suite. The test_recorder object will then be passed to test_publisher to store the results in a specific location (s3, long-lived-cluster, etc).
To-Do:
Issues Resolved
#340
Tests:
Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.