-
Notifications
You must be signed in to change notification settings - Fork 335
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
Improve automatic test suite's HTML reports #2903
Conversation
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.
This is awesome ! It's been a long time since we want this on our test report 😄
Thanks a lot :)
scripts/runtestsuite/runtestsuite.py
Outdated
|
||
self.file.write(self.__render(self.header_template, | ||
{'test-date': CURRENT_TIME, | ||
'python-version': utils.get_python_version(), | ||
'script-path': script_path, | ||
'script-version': VERSION, | ||
'script-version': "HELLO WORLD", #VERSION, |
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.
Hello ! 👋
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.
oops
scripts/runtestsuite/runtestsuite.py
Outdated
git_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD']) | ||
git_title = subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD']) |
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 happens if git is not installed or the appleseed version used is a release (we remove .git from the release) ?
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.
Ah, good call, I'll add some checks.
scripts/runtestsuite/runtestsuite.py
Outdated
|
||
def __write_stats(self, total_time, success, failures): | ||
success_rate = "{0}%".format(success) | ||
failures_str = "{0} out of {1} test scene(s)".format(failures[0], failures[1]) |
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.
Minor styling issue : here you use the prefix _str
, but not everywhere else. You can remove that prefix to have a more consistent code base :)
scripts/runtestsuite/runtestsuite.py
Outdated
self.file.flush() | ||
|
||
def __write_stats(self, total_time, success, failures): |
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.
By reading the argument names, I understand that success
and failures
are both of the same type (a rate or an amount). But when reading the code bellow, I realized that success
was a rate and failures
an amount. Do you think you can change the naming ot the value themeselves to have something more consistent ?
scripts/runtestsuite/runtestsuite.py
Outdated
git_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD']) | ||
git_title = subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD']) |
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.
Maybe you could create methods for those, as you already did for get_git_revision_hash
Co-authored-by: Kevin Masson <hi@oktomus.com>
Co-authored-by: Kevin Masson <hi@oktomus.com>
- Check scripts directory for git information instead of calling directory - Check that git is installed - Check that scripts directory is under git control - Variables renamed for clarity - Refactored various methods
Thanks for your feedback, @oktomus. I've attempted to address most of it in my latest commit. |
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.
Great work ! My last comments are mostly about style, to make sure it matches with the existing code base.
scripts/runtestsuite/runtestsuite.py
Outdated
def git_installed(directory): | ||
temp = 'git -C {0} --version'.format(directory) | ||
print(temp) | ||
return command_is_valid('git -C {0} --version'.format(directory)) | ||
|
||
def under_git_control(directory): | ||
return command_is_valid('git -C {0} rev-parse --is-inside-work-tree'.format(directory)) |
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.
def git_installed(directory): | |
temp = 'git -C {0} --version'.format(directory) | |
print(temp) | |
return command_is_valid('git -C {0} --version'.format(directory)) | |
def under_git_control(directory): | |
return command_is_valid('git -C {0} rev-parse --is-inside-work-tree'.format(directory)) | |
def is_git_installed(directory): | |
return command_is_valid('git -C {0} --version'.format(directory)) | |
def is_git_repository(directory): | |
return command_is_valid('git -C {0} rev-parse --is-inside-work-tree'.format(directory)) |
Here is a suggestion that remove the unused var and change the naming
scripts/runtestsuite/runtestsuite.py
Outdated
return command_output('git -C {0} rev-parse --abbrev-ref HEAD'.format(directory)) | ||
|
||
def command_output(command): | ||
output = "N/A" |
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.
output = "N/A" | |
output = None |
Isn't this more consistent with Python conventions ?
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.
This is what will be printed when the command fails (e.g. get_git_hash
fails). I thought it would be more consistent to have it print N\A
rather than none
.
I could have an if else
statement inside __write_header
but I thought that would make it unnecessarily messy, do you think it would be a better choice?
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 think it's a good idea to print "N/A" for the user. But here the method is command_output
and we don't expect it to improve the output by default.
scripts/runtestsuite/runtestsuite.py
Outdated
is_valid = 1 | ||
try: | ||
is_valid = subprocess.check_call( | ||
command.split(), | ||
stdin = subprocess.PIPE, | ||
stdout = open(os.devnull, 'wb'), | ||
stderr = subprocess.STDOUT) | ||
except (subprocess.CalledProcessError, OSError): | ||
pass | ||
|
||
return is_valid == 0 |
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.
Here is an alternative that doesn't require an int and is easier to understand
is_valid = 1 | |
try: | |
is_valid = subprocess.check_call( | |
command.split(), | |
stdin = subprocess.PIPE, | |
stdout = open(os.devnull, 'wb'), | |
stderr = subprocess.STDOUT) | |
except (subprocess.CalledProcessError, OSError): | |
pass | |
return is_valid == 0 | |
try: | |
command_output = subprocess.check_call( | |
command.split(), | |
stdin = subprocess.PIPE, | |
stdout = open(os.devnull, 'wb'), | |
stderr = subprocess.STDOUT) | |
return command_output == 0 | |
except (subprocess.CalledProcessError, OSError): | |
pass | |
return False |
Althouth, I don't understand why calling .split()
on command
os required, and why you need to specify the standard output and error 🤔
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.
As far as I can tell, subprocess
doesn't read spaces properly. Anything separated by a space, needs to be separate elements of an array.
I'm specifying the standard output and error so they don't get displayed in the terminal (and disrupt the nice table that gets printed). It is possible that this would be cleaner if I used another subprocess
method here, I'll have to investigate.
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.
Ok, good to know ! We can keep this, but maybe you can mention these important details directly in the code ?
scripts/runtestsuite/runtestsuite.py
Outdated
git_hash = git_title = "N/A" | ||
|
||
if git_installed(self.template_directory): | ||
if under_git_control(self.template_directory): | ||
git_hash = get_git_hash(self.template_directory) | ||
git_title = get_git_title(self.template_directory) | ||
else: | ||
git_hash = git_title = "N/A (Directory not under git control)" | ||
else: | ||
git_hash = git_title = "N/A (Git not installed)" |
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.
Do you think this is easier to understand ?
git_hash = git_title = "N/A" | |
if git_installed(self.template_directory): | |
if under_git_control(self.template_directory): | |
git_hash = get_git_hash(self.template_directory) | |
git_title = get_git_title(self.template_directory) | |
else: | |
git_hash = git_title = "N/A (Directory not under git control)" | |
else: | |
git_hash = git_title = "N/A (Git not installed)" | |
is_git_installed = git_installed(self.template_directory) | |
is_git_repository = under_git_control(self.template_directory) | |
git_hash = git_title = None | |
if is_git_installed and is_git_repository: | |
git_hash = get_git_hash(self.template_directory) | |
git_title = get_git_title(self.template_directory) | |
elif not is_git_installed: | |
git_hash = git_title = "N/A (Git not installed)" | |
else: | |
git_hash = git_title = "N/A (Directory not under git control)" |
scripts/runtestsuite/runtestsuite.py
Outdated
def __write_stats(self, total_time, success_rate, failures, rendered_scenes): | ||
success_rate = "{0}%".format(success_rate) | ||
failures = "{0} out of {1} test scene(s)".format(failures, rendered_scenes) | ||
self.file.write(self.__render(self.stats_template, | ||
{'total-time': format_duration(total_time), | ||
'success-rate': success_rate, | ||
'failures': failures})) |
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 can compute the success_rate here. Or, you can pass 3 count instead of 1 rate + 2 count, which is more consistent.
def __write_stats(self, total_time, success_rate, failures, rendered_scenes): | |
success_rate = "{0}%".format(success_rate) | |
failures = "{0} out of {1} test scene(s)".format(failures, rendered_scenes) | |
self.file.write(self.__render(self.stats_template, | |
{'total-time': format_duration(total_time), | |
'success-rate': success_rate, | |
'failures': failures})) | |
def __write_stats(self, total_time, success_test_count, failed_test_count, total_test_count): | |
success_rate = int(success_test_count / total_test_count * 100.0) | |
total_time_text = format_duration(total_time) | |
success_rate_text = "{0}%".format(success_rate) | |
failures_text = "{0} out of {1} test scene(s)".format(failed_test_count, total_test_count)})) | |
self.file.write(self.__render(self.stats_template, | |
{'total-time': total_time_text, | |
'success-rate': success_rate_text , | |
'failures': failures_text })) |
What do 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 think I mislead you in my last comment when suggesting to remove the suffix _str
. My bad
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 like your suggestion below of passing the values around in a class but I think it would still be useful to have the success rate
included, since the number is used more than once and the calculation is quite long (you also need to check for 0 rendered scenes to avoid a divide by 0).
scripts/runtestsuite/runtestsuite.py
Outdated
@@ -452,9 +509,11 @@ def render_test_scene(args, logger, report_writer, project_directory, project_fi | |||
# -------------------------------------------------------------------------------------------------- | |||
|
|||
def render_test_scenes(script_directory, args): | |||
rendered_scene_count = 0 | |||
rendered_scenes = 0 |
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.
It should remain as it was
rendered_scenes
suggest the variable contains multiple scenes, such as a list.
rendered_scenes = 0 | |
rendered_scene_count = 0 |
scripts/runtestsuite/runtestsuite.py
Outdated
end_time = datetime.datetime.now() | ||
|
||
success = 100.0 * passing_scene_count / rendered_scene_count if rendered_scene_count > 0 else 0.0 | ||
failures, rendered_scenes, success_rate, total_time = render_test_scenes(script_directory, args) |
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.
Some times, when you end up passing multiple argument everytime together, it's much easier to create an object.
Like class TestSuiteRunnerResult
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.
Yup, I like it.
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'm thinking maybe something shorter (I notice Logger
and not TestSuiteLogger
for instance). How about TestResult
or just Results
?
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.
Logger is something very common and speek for itself.
Results here is really only for the tests and doesn't speek for itself. You can't really use it ouside
Main changes are:
|
Thanks for the changes @russkev :) |
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.
Many thanks for that PR @russkev. This is a very good first contribution. Once merged, we should see your feature right here : http://testsuite.appleseedhq.net:8080/
The PR is ready to be merged for me, there is few styling changes I would suggest ( Results
class name, method names like increment
), but it's minor.
@dictoon Could you have a final look so that we can merge ?
scripts/runtestsuite/runtestsuite.py
Outdated
def __write_stats(self, results): | ||
total_time_text = results.total_time() | ||
success_rate_text = "{0}%".format(results.success_rate()) | ||
failures_text = "{0} out of {1} test scene(s)".format( | ||
results.failure_count(), | ||
results.total_count()) | ||
|
||
self.file.write(self.__render(self.stats_template, | ||
{'total-time': total_time_text, | ||
'success-rate': success_rate_text, | ||
'failures': failures_text})) |
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.
Nice :)
scripts/runtestsuite/runtestsuite.py
Outdated
success_rate_text = "{0}%".format(results.success_rate()) | ||
failures_text = "{0} out of {1} test scene(s)".format( | ||
results.failure_count(), | ||
results.total_count()) |
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.
results.total_count()) | |
results.total_test_count()) |
total_count
doesn't really mean anything on its own right ?
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.
Good point
Thanks for the kind words @oktomus I'm happy to help. Also thank you for your detailed reviews. Having read Clean Code, I very much appreciate the importance of good naming so I'm thankful to have guidance in that area. I just made a few tweaks. Mainly changing a few names and adding some comments. Also, what do you think about also implementing a |
You mean including successful tests in the html report ? Right now, showing only the failed tests is very useful because you can easily see if something is wrong or not. For a complete report, there is always the txt report : http://testsuite.appleseedhq.net:8080/current-job/sandbox/tests/test%20scenes/testsuite_script_log.txt. But maybe you have a design in mind that allow to get all the tests, while still being able to quickly see the failures ? Although I'm not sure how seeing all tests could be used for |
I have to admit, I haven't spent much time playing with the tool in practice so I'm not familiar with how it's used. I thought that it would be nice to have all the information from the tests in one nicely presented html file instead of split up over multiple documents. The way I was thinking of doing it was to have the successful tests be just a single line and the failures would have the comparison images, maybe some red text, so that it would be easy to differentiate. |
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 went through your code one last time with @dictoon and we have some minor suggestions. After that, we can merge it :) Thanks again, great work!
scripts/runtestsuite/runtestsuite.py
Outdated
def command_is_valid(command): | ||
try: | ||
command_return = subprocess.check_call( | ||
# Ensure space seperated arguments are separate elements of an array |
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.
# Ensure space seperated arguments are separate elements of an array | |
# Ensure space separated arguments are separate elements of an array |
scripts/runtestsuite/runtestsuite.py
Outdated
@@ -262,8 +369,6 @@ def __make_update_command(self, output_filepath, reference_filepath): | |||
return 'copy /Y "{0}" "{1}"'.format(output_filepath, reference_filepath) | |||
else: | |||
return 'cp "{0}" "{1}"'.format(output_filepath, reference_filepath) | |||
|
|||
|
|||
# -------------------------------------------------------------------------------------------------- |
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.
Reverting that change
# -------------------------------------------------------------------------------------------------- | |
# -------------------------------------------------------------------------------------------------- |
scripts/runtestsuite/runtestsuite.py
Outdated
self.start_time = datetime.datetime.min | ||
self.end_time = datetime.datetime.min | ||
|
||
def increment_total_count(self): |
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.
def increment_total_count(self): | |
def increment_rendered_count(self): |
scripts/runtestsuite/runtestsuite.py
Outdated
def increment_total_count(self): | ||
self.rendered += 1 | ||
|
||
def increment_successes_count(self): |
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.
def increment_successes_count(self): | |
def increment_success_count(self): |
<h2>Summary</h2> | ||
<table class="details"> | ||
<tr> | ||
<td>Success rate</td> |
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.
<td>Success rate</td> | |
<td>Success Rate</td> |
<td>Source commit</td> | ||
<td>{git-title}</td> | ||
</tr> | ||
<tr> | ||
<td>Source commit hash</td> | ||
<td>{git-hash}</td> |
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.
<td>Source commit</td> | |
<td>{git-title}</td> | |
</tr> | |
<tr> | |
<td>Source commit hash</td> | |
<td>{git-hash}</td> | |
<td>Source Commit</td> | |
<td>{git-title}</td> | |
</tr> | |
<tr> | |
<td>Source Commit Hash</td> | |
<td>{git-hash}</td> |
Okay, I've made those changes and merged the latest master. Let me know if there's anything else I can do. |
Addresses issue #2853 . Git hash and title now added to HTML report.
Addresses issue #2844. I replicated the the statistics that get displayed in the console in the HTML report. I originally intended the info to be at the top of the page but that needed a lot more work than putting it at the bottom, so I went with that instead.