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

Draft: Feat/catch2 compat #169

Closed
wants to merge 4 commits into from

Conversation

CrustyAuklet
Copy link
Member

Very rought draft, but I wanted to open it so others can look and comment.

This is work to make the Catch2 XML output match up with Catch2. Like the other open MRs I notices this when integrating with my teams IDE and (more importantly) CI test reporting systems.

Without these changes CI and IDEs work ok, but failures in a section just fail the whole test. When we were using Catch2 a failure would be specific to a section allowing us to quickly drill down to the problem. So looking into the difference it is just the lack of section output in the "high" verbosity mode. Catch2 outputs section XML data by default.

It was fairly easy to make it work with basic testing, pass and fail. There are more complex edge cases around skips, nested sections, exceptions being thrown, and different verbosity levels. I am still ironing those out and need to make sure there is testing for it all.

As I have time this week i will add some examples here, and some pictures to show the different in CI/IDEs.

@cschreib
Copy link
Member

Thanks for spotting this. I could have sworn I got the output for sections to match Catch2 when I last tested this; but perhaps I had looked at a too simple case and missed the bigger picture.

As I have time this week i will add some examples here, and some pictures to show the different in CI/IDEs.

It would be very interesting to compare the actual XML output between the two in a real-world case; the test project I use for testing and benchmarks doesn't actually use sections.

@CrustyAuklet CrustyAuklet force-pushed the feat/catch2-compat branch 3 times, most recently from 8b1d448 to cded7ab Compare May 17, 2024 02:56
Comment on lines +163 to +165
<Failure filename="*testing_reporters.cpp" line="*">
unexpected std::exception caught; message: unexpected error
</Failure>
Copy link
Member

@cschreib cschreib May 27, 2024

Choose a reason for hiding this comment

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

I think this message is now printed too late; I believe we want it to appear inside the <Section> where the exception was thrown, as it was before. See comment in ~section_entry_checker().

Copy link
Member

Choose a reason for hiding this comment

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

Fixed.

Comment on lines +19 to +30
state.reg.report_callback(
state.reg, event::section_ended{
state.sections.current_section.back().id,
state.sections.current_section.back().location, true});
Copy link
Member

@cschreib cschreib May 27, 2024

Choose a reason for hiding this comment

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

See comment in the approval tests; I think this is done too soon. When an exception is in flight, it has not been reported yet, but we now close the section immediately. This results in all unhandled exceptions being reported at the root of the test case, rather than inside the section from which it originated.

I believe this particular case (close the section when unwinding an exception) can be handled in the registry instead, to solve this problem. That would be here:

} catch (const std::exception& e) {
report_assertion(false, "unexpected std::exception caught; message: ", e.what());
} catch (...) {
report_assertion(false, "unexpected unknown exception caught");
}

Also, is this particular path currently missing the asserts, failures, and allowed_failures counts? (also duration, skips)

Copy link
Member

Choose a reason for hiding this comment

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

Fixed.

@@ -3,12 +3,21 @@

#include "snitch/snitch_config.hpp"
#include "snitch/snitch_test_data.hpp"
#if SNITCH_WITH_TIMINGS
# include <chrono>
Copy link
Member

Choose a reason for hiding this comment

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

We had, so far, managed to avoid including <chrono> in public headers. Could we keep it that way, to keep compilation time down? I just ran some tests, measuring GCC time to parse an empty .cpp with #include:

  • include snitch_all.hpp as of today's main branch: 38ms.
  • same, but add <chrono>: 46ms (+8ms, or +22%).

This feels like a high price to pay for just storing a time point. We could perhaps store the numerical value of the time point in the struct here, as an anonymous std::size_t or similar that is >= sizeof(std::chrono::steady_clock::rep), and then convert back and forth to actual time points in the *.cpp files.

Copy link
Member Author

Choose a reason for hiding this comment

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

Makes sense, I see that <type_traits> is included already so I use that instead to create a signed version of size_t (since not all platforms define ssize_t). I added some aliases in the snitch::impl for chrono in the cpp file as well to make the chrono code less verbose and avoid bugs if anyone wants to change clocks or duration types in the future.

Copy link
Member

Choose a reason for hiding this comment

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

I ended up adding a new file snitch_time.cpp to wrap this up, since we needed chrono functionality elsewhere too (registry). In the end, all we need is a simple API with a function returning the "current time point" in some unspecified unit (I picked "number of nanoseconds since test run start" -- always positive with a steady_clock so a std::size_t is OK, and will only wrap around after ~600 years of execution, also OK) and another function to calculate the time difference between two time points.

Comment on lines +151 to +152
[&](const snitch::event::section_started&) {},
[&](const snitch::event::section_ended&) {},
Copy link
Member

@cschreib cschreib May 27, 2024

Choose a reason for hiding this comment

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

TeamCity uses the following:

  • ##teamcity[blockOpened name='...' description='...']
  • ##teamcity[blockClosed name='...']

Then we can probably remove the ad-hoc printing of sections in print_assertion()

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 the print_assertion() is see where it iterates over the sections, but I don't see how it outputs the test you show here. I don't see and "blockOpened" or "blockCosed"?

Copy link
Member

Choose a reason for hiding this comment

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

Got it :)

Comment on lines +29 to +39
asserts = state.asserts - asserts;
failures = state.failures - failures;
allowed_failures = state.allowed_failures - allowed_failures;
Copy link
Member

Choose a reason for hiding this comment

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

This gives two different meanings to section_entry_checker::asserts/failures/allowed_failures depending on context: on creation they hold the initial state, and on destruction they hold the difference. This will be a source of confusion, I fear. But in fact, we only need to store the initial state (and should probably rename the member variables as such, e.g. initial_sate.asserts/initial_state.failures/...); the difference computed here could be stored in local variables.

Copy link
Member

Choose a reason for hiding this comment

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

Alternatively, we could instead store the actual number of assertions (etc) that were recorded, and make the registry propagate the counts to all open sections in register_assertion(). This is a little bit more work for the CPU, but the code might be simpler.

Copy link
Member Author

Choose a reason for hiding this comment

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

Alternatively, we could instead store the actual number of assertions (etc) that were recorded, and make the registry propagate the counts to all open sections in register_assertion(). This is a little bit more work for the CPU, but the code might be simpler.

This also might be necessary since I am noticing in my tests that Catch2 counts these things cumulatively for nested sections but with this strategy snitch does not.

given

SECTION("Section1") {
    CHECK(true);
    SECTION("Section1.1") {
        CHECK(false);
    }
}

this code outputs

    <Section name="Section1" filename="all_fail.cpp" line="10">
      <Section name="Section1.1" filename="all_fail.cpp" line="12">
        <Expression success="false" type="CHECK" filename="all_fail.cpp" line="13">
          <Original>
            false
          </Original>
          <Expanded>
            false
          </Expanded>
        </Expression>
        <OverallResults successes="0" failures="1" expectedFailures="0" skipped="false" durationInSeconds=<TIME>/>
      </Section>
      <OverallResults successes="0" failures="0" expectedFailures="0" skipped="false" durationInSeconds=<TIME>/>
    </Section>

Catch2:

    <Section name="Section1" filename="all_fail.cpp" line="10">
      <Section name="Section1.1" filename="all_fail.cpp" line="12">
        <Expression success="false" type="CHECK" filename="all_fail.cpp" line="13">
          <Original>
            false
          </Original>
          <Expanded>
            false
          </Expanded>
        </Expression>
        <OverallResults successes="0" failures="1" expectedFailures="0" skipped="false" durationInSeconds=<TIME>/>
      </Section>
      <OverallResults successes="1" failures="1" expectedFailures="0" skipped="false" durationInSeconds=<TIME>/>
    </Section>

Copy link
Member Author

Choose a reason for hiding this comment

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

if we are counting the actual count for each section, then that needs to happen in the registry too? I don't see a way to do it in the section_entry_checker since it is only called on entry and exit.

Copy link
Member

Choose a reason for hiding this comment

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

Yep; I've implemented this so the registry does the propagation of assert counts to all currently open sections.

src/snitch_section.cpp Outdated Show resolved Hide resolved
Comment on lines 44 to 60
state.reg.report_callback(
state.reg,
event::section_ended{
data.id, data.location, false, asserts, failures, allowed_failures, duration});
#else
state.reg.report_callback(
state.reg,
event::section_ended{data.id, data.location, asserts, failures, allowed_failures});
Copy link
Member

Choose a reason for hiding this comment

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

I think all the reporting logic should be outsourced to registry; the report_callback isn't really meant to be used outside the registry class (it is exposed as a public member primarily so it can be assigned, otherwise it would be private). We probably want helper members functions like registry::report_section_entered / registry::report_section_exited to achieve this.

Copy link
Member Author

Choose a reason for hiding this comment

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

This sounds much better. Adding the reporting here was the stuff I was most uncomfortable with and it seems very hacky.

Copy link
Member

Choose a reason for hiding this comment

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

Done

@CrustyAuklet
Copy link
Member Author

thanks for the feedback, I will look it over in depth this coming week.

I've been working on making tests and comparing Catch2 to Snitch output in a repo on my account here: https://github.com/CrustyAuklet/snitch-xml-output. So far it's pretty close but I will have to add some more variations, especially to cover the exception stuff.

@cschreib
Copy link
Member

Hi there! Just wondering if this was still on your mind. Let me know if not, and I can take over.

@CrustyAuklet
Copy link
Member Author

It is, I have just had a ton on my plate. I am going to try to re-familiarize myself with it this afternoon and see if have any questions on your feedback.

I won't be offended at all if you think it's an easy fix for you to do and you want to get it done 😄

Catch2 has a section enter and exit event. This allows for finer resolution
display of test failures in IDEs and CI dashboards without giving up the
benefits of using sections.
As suggested in code review, don't include chrono in the snitch headers
since this increases compile times. Use a basic integral representation
type and then convert back and forth to chrono types in the source files
when actually reading clocks or calculating times.
@CrustyAuklet
Copy link
Member Author

fixed some of the small easy stuff and rebased, but spent a lot of time running some example tests in that other repo with exceptions. I think most of this code needs to be moved to the registry like you said.

@cschreib
Copy link
Member

cschreib commented Sep 13, 2024

Thanks for looking at it further. Does snitch reporting work as expected in your tests now? If so, I can do another pass of review, and if all looks fine, I can also take care of the final refactoring.

FYI: I just hit some of the issues you were having, so I can confirm (if this was even needed!) that things are currently broken on the main branch. I'm implementing a test runner for SublimeText, and for the sake of the exercise, I implemented a test adaptor for Catch2 first, and then tried to use it with snitch without touching it. Didn't work out of the box! I'm hoping this will be solved with your PR.

@CrustyAuklet
Copy link
Member Author

Without exceptions it works as expected and I am using it on some of my work projects. But with exceptions It doesn't match up with the output of Catch2, and that is where I got a bit stuck and maybe the code does need to move into the registry as you suggested.

The test using exceptions in the repo https://github.com/CrustyAuklet/snitch-xml-output shows some of the differences. Catch2 is more verbose but the real issue is that is seems that exceptions thrown from within sections are not output until the parent section.

Here are the output XML files from that test:
exceptions.catch2.txt
exceptions.snitch.txt

@cschreib
Copy link
Member

cschreib commented Sep 27, 2024

I believe the remaining issues are now sorted if you try the branch cschreib/catch2-compat. I created a PR for it #183 to get the CI runs. If you have no objection, I suggest switching to the new PR and closing this one (unless you feel like doing the git magic to pull my changes back into your branch; you'll need to rebase though). If you wanted to review the changes I pushed over your last commit, feel free!

By the way, if you need to do another PR later for some other change, you should have permissions to create a branch direclty in this repo without having to work from a fork. This would make it possible for us to work on the same branch when needed (which I could not do here, since this PR's branch belongs exclusively to you).

@cschreib
Copy link
Member

Superseded by #183; thanks again for kick-starting this!

@cschreib cschreib closed this Oct 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Catch2 XML reporter does not output sections properly
2 participants