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

Print pending groups in-line and include their reason, also add progress bar to the runner #9796

Merged
merged 7 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 6 additions & 7 deletions distribution/lib/Standard/Test/0.0.0-dev/src/Helpers.enso
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,25 @@ import project.Test.Test
import project.Test_Reporter
import project.Test_Result.Test_Result

run_group : Group -> Vector Test_Result
run_group (group : Group) =
assert group.is_pending.not
run_specs_from_group group.specs group


run_specs_from_group : Vector Spec -> Group -> Vector Test_Result
run_specs_from_group (specs : Vector Spec) (group : Group) =
run_specs_from_group : Vector Spec -> Group -> Any -> Vector Test_Result
run_specs_from_group (specs : Vector Spec) (group : Group) progress_reporter =
assert (group.is_pending.not)
case specs.is_empty of
True -> []
False ->
test_results = specs.map spec->
assert (group_contains_spec group spec)
progress_reporter.report_progress (group.name+": "+spec.name)
pair = run_spec spec
spec_res = pair.second
time_taken = pair.first
Test_Result.Impl group.name spec.name spec_res time_taken

progress_reporter.report_progress (group.name+": (Teardown)") increment=0
# Invoke the teardown of the group
group.teardown Nothing
progress_reporter.clear
test_results


Expand Down
40 changes: 24 additions & 16 deletions distribution/lib/Standard/Test/0.0.0-dev/src/Suite.enso
Original file line number Diff line number Diff line change
Expand Up @@ -61,46 +61,54 @@ type Suite
run_with_filter self (filter : (Text | Nothing) = Nothing) (should_exit : Boolean = True) -> (Boolean | Nothing) =
config = Suite_Config.from_environment

# Map of groups to vector of specs that match the filter
matching_specs = self.groups.fold Map.empty map-> group->
# List of pairs of groups and their specs that match the filter
matching_specs = self.groups.flat_map group->
group_matches = name_matches group.name filter
case group_matches of
True ->
# Include all the specs from the group
map.insert group group.specs
[[group, group.specs]]
False ->
# Try to include only some specs from the group
matched_specs = group.specs.filter spec->
name_matches spec.name filter
case matched_specs.is_empty of
True -> map
True -> []
False ->
assert (map.contains_key group . not)
map.insert group matched_specs
[[group, matched_specs]]

progress_reporter = case Test_Reporter.is_terminal_interactive of
True ->
matching_spec_count = matching_specs.map (p-> p.second.length) . fold 0 (+)
Test_Reporter.Command_Line_Progress_Reporter.make matching_spec_count
False ->
Test_Reporter.Ignore_Progress_Reporter

all_results_bldr = Vector.new_builder
junit_sb_builder = if config.should_output_junit then StringBuilder.new else Nothing
Test_Reporter.wrap_junit_testsuites config junit_sb_builder <|
matching_specs.each_with_key group-> specs->
if group.is_pending.not then
results = Helpers.run_specs_from_group specs group
Test_Reporter.print_report results config junit_sb_builder
all_results_bldr.append_vector_range results
matching_specs.each p->
group = p.first
specs = p.second
case group.is_pending of
False ->
results = Helpers.run_specs_from_group specs group progress_reporter
Test_Reporter.print_report results config junit_sb_builder
all_results_bldr.append_vector_range results
True ->
Test_Reporter.print_pending_group group config junit_sb_builder

all_results = all_results_bldr.to_vector
succ_tests = all_results.filter (r-> r.is_success) . length
failed_tests = all_results.filter (r-> r.is_fail) . length
skipped_tests = all_results.filter (r-> r.is_pending) . length
pending_groups = matching_specs.filter (p-> p.first.is_pending) . length
case should_exit of
True ->
IO.println <| succ_tests.to_text + " tests succeeded."
IO.println <| failed_tests.to_text + " tests failed."
IO.println <| skipped_tests.to_text + " tests skipped."
pending_groups = matching_specs.keys.filter (group-> group.is_pending)
pending_groups_details = case pending_groups.is_empty of
True -> "."
False -> ": " + (pending_groups.map (it-> it.name) . to_text)
IO.println <| pending_groups.length.to_text + " groups skipped" + pending_groups_details
IO.println <| pending_groups.to_text + " groups skipped."
exit_code = if failed_tests > 0 then 1 else 0
System.exit exit_code
False ->
Expand Down
97 changes: 88 additions & 9 deletions distribution/lib/Standard/Test/0.0.0-dev/src/Test_Reporter.enso
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ private

from Standard.Base import all
import Standard.Base.Runtime.Context
import Standard.Base.Runtime.Ref.Ref
from Standard.Base.Runtime import assert

import project.Group.Group
import project.Internal.Stack_Trace_Helpers
import project.Spec_Result.Spec_Result
import project.Suite_Config.Suite_Config
import project.Test.Test
import project.Test_Result.Test_Result

polyglot java import java.lang.StringBuilder
polyglot java import java.lang.System as Java_System

## PRIVATE
Write the JUnit XML header.
Expand Down Expand Up @@ -41,6 +44,9 @@ green text =
highlighted text =
'\u001b[1;1m' + text + '\u001b[0m'

grey text =
'\u001b[90m' + text + '\u001b[0m'

maybe_red_text (text : Text) (config : Suite_Config) =
if config.use_ansi_colors then (red text) else text

Expand All @@ -50,6 +56,9 @@ maybe_green_text (text : Text) (config : Suite_Config) =
maybe_highlighted_text (text : Text) (config : Suite_Config) =
if config.use_ansi_colors then (highlighted text) else text

maybe_grey_text (text : Text) (config : Suite_Config) =
if config.use_ansi_colors then (grey text) else text

## Print result for a single Spec run
print_single_result : Test_Result -> Suite_Config -> Nothing
print_single_result (test_result : Test_Result) (config : Suite_Config) =
Expand All @@ -74,7 +83,7 @@ print_single_result (test_result : Test_Result) (config : Suite_Config) =
IO.println (decorate_stack_trace details)
Spec_Result.Pending reason ->
if config.print_only_failures.not then
IO.println (" - [PENDING] " + test_result.spec_name)
IO.println (maybe_grey_text (" - [PENDING] " + test_result.spec_name) config)
IO.println (" Reason: " + reason)


Expand All @@ -96,6 +105,20 @@ print_report (test_results : Vector Test_Result) (config : Suite_Config) (builde
results_per_group.each_with_key group_name-> group_results->
print_group_report group_name group_results config builder

## Prints a pending group, optionally writing it to a jUnit XML output.
print_pending_group : Group -> Vector -> Suite_Config -> (StringBuilder | Nothing) -> Nothing
print_pending_group group config builder =
assert group.pending.is_nothing.not "Group in print_pending_group should be pending"
if config.should_output_junit then
assert builder.is_nothing.not "Builder must be specified when JUnit output is enabled"
builder.append (' <testsuite name="' + (escape_xml group.name inside_attribute=True) + '" timestamp="' + (Date_Time.now.format "yyyy-MM-dd'T'HH:mm:ss") + '"')
builder.append (' tests="0" disabled="1" errors="0" time="0.0">\n')
builder.append (' <testcase name="PENDING" time="0.0">\n')
builder.append (' <skipped message="Reason: '+(escape_xml group.pending inside_attribute=True)+'"/>\n')
builder.append (' </testcase>\n')
builder.append ' </testsuite>\n'
IO.println <| maybe_grey_text ("[PENDING] " + group.name) config
IO.println (" Reason: " + group.pending)

## Prints report for test_results from a single group.

Expand All @@ -109,28 +132,27 @@ print_group_report group_name test_results config builder =
acc + res.time_taken
if config.should_output_junit then
assert builder.is_nothing.not "Builder must be specified when JUnit output is enabled"
builder.append (' <testsuite name="' + (escape_xml group_name) + '" timestamp="' + (Date_Time.now.format "yyyy-MM-dd'T'HH:mm:ss") + '"')
builder.append (' <testsuite name="' + (escape_xml group_name inside_attribute=True) + '" timestamp="' + (Date_Time.now.format "yyyy-MM-dd'T'HH:mm:ss") + '"')
builder.append (' tests="' + test_results.length.to_text + '"')
builder.append (' disabled="' + test_results.filter _.is_pending . length . to_text + '"')
builder.append (' errors="' + test_results.filter _.is_fail . length . to_text + '"')
builder.append (' time="' + total_time.total_seconds.to_text + '"')
builder.append ('>\n')

test_results.each result->
builder.append (' <testcase name="' + (escape_xml result.spec_name) + '" time="' + ((result.time_taken.total_milliseconds / 1000.0).to_text) + '">')
builder.append (' <testcase name="' + (escape_xml result.spec_name inside_attribute=True) + '" time="' + ((result.time_taken.total_milliseconds / 1000.0).to_text) + '">')
case result.spec_result of
Spec_Result.Success -> Nothing
Spec_Result.Failure msg details ->
escaped_message = escape_xml msg . replace '\n' '&#10;'
builder.append ('\n <failure message="' + escaped_message + '">\n')
builder.append ('\n <failure message="' + (escape_xml msg inside_attribute=True) + '">\n')
# We always print the message again as content - otherwise the GitHub action may fail to parse it.
builder.append (escape_xml msg)
if details.is_nothing.not then
## If there are additional details, we print them as well.
builder.append '\n\n'
builder.append (escape_xml details)
builder.append '\n </failure>\n'
Spec_Result.Pending msg -> builder.append ('\n <skipped message="' + (escape_xml msg) + '"/>\n ')
Spec_Result.Pending msg -> builder.append ('\n <skipped message="' + (escape_xml msg inside_attribute=True) + '"/>\n ')
builder.append ' </testcase>\n'
builder.append ' </testsuite>\n'

Expand All @@ -154,6 +176,63 @@ print_group_report group_name test_results config builder =

## PRIVATE
Escape Text for XML
escape_xml : Text -> Text
escape_xml input =
input.replace '&' '&amp;' . replace '"' '&quot;' . replace "'" '&apos;' . replace '<' '&lt;' . replace '>' '&gt;'
escape_xml : Text -> Boolean -> Text
escape_xml input inside_attribute=False =
escaped = input.replace '&' '&amp;' . replace '"' '&quot;' . replace "'" '&apos;' . replace '<' '&lt;' . replace '>' '&gt;'
if inside_attribute then escaped.replace '\n' '&#10;' else escaped

## PRIVATE
progress_width = 70

## PRIVATE
print_progress current_progress total_count status_text =
total_count_as_text = total_count.to_text
counter_width = total_count_as_text.length
current_progress_as_text = current_progress.to_text.pad counter_width at=Location.Start
line = " ("+ current_progress_as_text + " / " + total_count_as_text + ") " + status_text
truncated_line = if line.length <= progress_width then line else
line.take (progress_width - 3) + '...'

Java_System.out.print '\r'
Java_System.out.print (' ' * progress_width)
Java_System.out.print '\r'
Java_System.out.print truncated_line
Java_System.out.print '\r'

## PRIVATE
clear_progress =
Java_System.out.print '\r'
Java_System.out.print (' ' * progress_width)
Java_System.out.print '\r'

## PRIVATE
type Ignore_Progress_Reporter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please ensure that Ignore_Progress_Reporter is used on the CI, but the coloring is still there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 7aeecf2 I have changed it so that the progress bar is displayed if System.console() != null. This should be enough to avoid it running on CI or piped and better than adding yet another env var.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have inspected the runs.

Indeed before the progress was displayed (badly) on CI:
image

But after the above commit all seems fine again:
image

I think the automatic detection of console is even better than an env var.

## PRIVATE
report_progress self (status_text : Text) (increment : Integer = 1) =
_ = [increment, status_text]
Nothing

## PRIVATE
clear = Nothing

## PRIVATE
type Command_Line_Progress_Reporter
## PRIVATE
Value current_progress total_count

## PRIVATE
make total_expected =
Command_Line_Progress_Reporter.Value (Ref.new 0) total_expected

## PRIVATE
report_progress self (status_text : Text) (increment : Integer = 1) =
self.current_progress.modify (+increment)
print_progress self.current_progress.get self.total_count status_text

## PRIVATE
clear self = clear_progress

## PRIVATE
Checks if the current process is running in an interactive terminal session.
is_terminal_interactive -> Boolean =
Java_System.console != Nothing
Loading