diff --git a/distribution/lib/Standard/Test/0.0.0-dev/src/Helpers.enso b/distribution/lib/Standard/Test/0.0.0-dev/src/Helpers.enso index 8a846d3d9b5a..2f4a2bef3735 100644 --- a/distribution/lib/Standard/Test/0.0.0-dev/src/Helpers.enso +++ b/distribution/lib/Standard/Test/0.0.0-dev/src/Helpers.enso @@ -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 diff --git a/distribution/lib/Standard/Test/0.0.0-dev/src/Suite.enso b/distribution/lib/Standard/Test/0.0.0-dev/src/Suite.enso index 66c63e5a4eca..1ff92a5ea5ca 100644 --- a/distribution/lib/Standard/Test/0.0.0-dev/src/Suite.enso +++ b/distribution/lib/Standard/Test/0.0.0-dev/src/Suite.enso @@ -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 -> diff --git a/distribution/lib/Standard/Test/0.0.0-dev/src/Test_Reporter.enso b/distribution/lib/Standard/Test/0.0.0-dev/src/Test_Reporter.enso index 4d6824c2ceca..16e6f9979811 100644 --- a/distribution/lib/Standard/Test/0.0.0-dev/src/Test_Reporter.enso +++ b/distribution/lib/Standard/Test/0.0.0-dev/src/Test_Reporter.enso @@ -2,8 +2,10 @@ 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 @@ -11,6 +13,7 @@ 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. @@ -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 @@ -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) = @@ -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) @@ -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 (' \n') + builder.append (' \n') + builder.append (' \n') + builder.append (' \n') + builder.append ' \n' + IO.println <| maybe_grey_text ("[PENDING] " + group.name) config + IO.println (" Reason: " + group.pending) ## Prints report for test_results from a single group. @@ -109,7 +132,7 @@ 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 (' \n') test_results.each result-> - builder.append (' ') + builder.append (' ') case result.spec_result of Spec_Result.Success -> Nothing Spec_Result.Failure msg details -> - escaped_message = escape_xml msg . replace '\n' ' ' - builder.append ('\n \n') + builder.append ('\n \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 @@ -130,7 +152,7 @@ print_group_report group_name test_results config builder = builder.append '\n\n' builder.append (escape_xml details) builder.append '\n \n' - Spec_Result.Pending msg -> builder.append ('\n \n ') + Spec_Result.Pending msg -> builder.append ('\n \n ') builder.append ' \n' builder.append ' \n' @@ -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 '&' '&' . replace '"' '"' . replace "'" ''' . replace '<' '<' . replace '>' '>' +escape_xml : Text -> Boolean -> Text +escape_xml input inside_attribute=False = + escaped = input.replace '&' '&' . replace '"' '"' . replace "'" ''' . replace '<' '<' . replace '>' '>' + if inside_attribute then escaped.replace '\n' ' ' 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 + ## 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