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

Add BlockListener support... #1575

Open
wants to merge 18 commits into
base: master
Choose a base branch
from

Conversation

leonard84
Copy link
Member

This feature allows extension authors to register a IBlockListener for
a feature to observe the execution of a feature in more detail.
This surfaces some of Spock's idiosyncrasies, for example interaction
assertions are actually setup right before entering the preceding
when-block as well as being evaluated on leaving the when-block
before actually entering the then-block.

The only valid block description is a constant String, although some
users mistakenly try to use a dynamic GString. Using anything other
than a String, will be treated as a separate statement and thus ignored.

fixes #538
fixes #111

@codecov
Copy link

codecov bot commented Feb 16, 2023

Codecov Report

Attention: Patch coverage is 79.57746% with 29 lines in your changes missing coverage. Please review.

Project coverage is 81.85%. Comparing base (2c7db77) to head (d6f4c9d).
Report is 100 commits behind head on master.

Files Patch % Lines
...rg/spockframework/runtime/DataIteratorFactory.java 40.00% 9 Missing ⚠️
.../java/org/spockframework/runtime/ErrorContext.java 63.15% 7 Missing ⚠️
...main/java/org/spockframework/compiler/AstUtil.java 50.00% 2 Missing ⚠️
...ava/org/spockframework/compiler/SpecAnnotator.java 33.33% 1 Missing and 1 partial ⚠️
...java/org/spockframework/compiler/SpecRewriter.java 94.73% 0 Missing and 2 partials ⚠️
...ockframework/runtime/extension/IBlockListener.java 0.00% 2 Missing ⚠️
...va/org/spockframework/runtime/model/BlockInfo.java 66.66% 2 Missing ⚠️
.../java/org/spockframework/compiler/model/Block.java 83.33% 1 Missing ⚠️
...org/spockframework/runtime/PlatformSpecRunner.java 75.00% 1 Missing ⚠️
...va/org/spockframework/runtime/model/ErrorInfo.java 75.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1575      +/-   ##
============================================
+ Coverage     80.44%   81.85%   +1.40%     
- Complexity     4337     4603     +266     
============================================
  Files           441      448       +7     
  Lines         13534    14444     +910     
  Branches       1707     1821     +114     
============================================
+ Hits          10888    11823     +935     
+ Misses         2008     1950      -58     
- Partials        638      671      +33     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@kriegaex
Copy link
Contributor

Hi @leonard84. I cannot perform a formal code review, because I am not a committer, but I hope that during the weekend I can find a small time slice to build and play around with it. I simply wanted to say thanks in advance for taking care of this feature request. It has not gone unnoticed.

Quick question: Are you planning to add more commits to this PR? Code changes? User manual? I am just asking, not demanding anything. I simply do not want to start testing too early. As for the user manual, I can of course take a look at the unit tests and take it from there. But that might not be true for all future extension developers, I am just speaking for myself.

@leonard84
Copy link
Member Author

@kriegaex I'll write some documentation for it, but I mainly wanted to get feedback on the feature first, like the one from @szpak concerning the usability.

try {
org.spockframework.runtime.SpockRuntime.callEnterBlock(this.getSpecificationContext(), new org.spockframework.runtime.model.BlockInfo(org.spockframework.runtime.model.BlockKind.WHEN, []))
foobar = this.foobar()
org.spockframework.runtime.SpockRuntime.callExitBlock(this.getSpecificationContext(), new org.spockframework.runtime.model.BlockInfo(org.spockframework.runtime.model.BlockKind.WHEN, []))
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 won't be called, fixing this makes for some awkward interactions between the individual AST transformations,

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 tried moving the blockListener logic where the blocks are written back in SpecRewriter#L388, however this didn't work for some cases, e.g. cleanup as it already replaced all Blocks with an anonymous block.

@leonard84 leonard84 force-pushed the add-block-listener-support branch from c3f30eb to 0f3197e Compare May 7, 2023 05:41
@szpak
Copy link
Member

szpak commented Oct 2, 2023

As this PR seems a little bit stalled, I pushed the code I initially created ~6 months ago as a PoC for the BlockListener support. It a very raw version (with a lot of diagnostic stuff to be amended in the future), but functional in some basic scenarios. The JPA session, where requested, is flushed after the when block.

https://github.com/szpak/spock-jpa-flush-enforcer/tree/preview1

I would like to awake discussion about future shape of this PR.

@leonard84 @kriegaex WDYT?

@leonard84
Copy link
Member Author

It mostly hangs on the problems that adding the exit listener introduces #1575 (comment)

@leonard84
Copy link
Member Author

I think I've fixed the issues, but I need another fresh set of eyes to verify that everything looks good, I've stared at too many snapshots already.

@leonard84
Copy link
Member Author

@AndreasTu, as I mentioned earlier, I'll write some documentation and polish it later. Thanks for your comments in any case.

However, I'm looking for a review of the correctness of the implementation and generated code and general usability.

Copy link
Member

@szpak szpak left a comment

Choose a reason for hiding this comment

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

I've switched my experimental (and heavily work-in-progress) extension to the latest version and it still works as expected without any modification. Nice.

I will think about the possible corner cases, I might encounter there.

Btw, I wonder, if it is still a recommended way to decide if the block listener was intended for the current iteration?

Copy link
Member

@Vampire Vampire left a comment

Choose a reason for hiding this comment

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

Some things missing in multiple places:

  • license headers
  • JavaDoc
  • documentation
  • @Beta
  • @since

Copy link
Member

Vampire commented Mar 21, 2024

Btw. could we also support where (and upcoming filter) blocks in a sensible way?

@leonard84
Copy link
Member Author

Btw. could we also support where (and upcoming filter) blocks in a sensible way?

I don't think it would be intuitive or really helpful.
They run outside the normal iteration.
When would you enter or leave the block?

  • only once when creating the data provider
  • on every data iterator's iteration?

However, you are most familiar with that part of the code.

@Vampire
Copy link
Member

Vampire commented Mar 22, 2024

When would you enter or leave the block?

No idea. :-D
I guess multiple times.
Maybe with an additional phase enum.
One for creating the data iterators.
One for getting the next set of data variable values.

But we can also start without and see where and how need arises.
But then it needs time to land in production version with our release cadence. :-D

@leonard84
Copy link
Member Author

Forgot to publish my responses 😅

@leonard84
Copy link
Member Author

Btw, I wonder, if it is still a recommended way to decide if the block listener was intended for the current iteration?

@szpak, can you motivate this use case more? At first glance, I'd try to register a block listener that can handle all iterations instead of one per iteration.

@szpak
Copy link
Member

szpak commented Apr 25, 2024

@szpak, can you motivate this use case more? At first glance, I'd try to register a block listener that can handle all iterations instead of one per iteration.

Hmm, I think the main problem was to get the invocation instance to obtain the current field instance. As a result, I put IBlockListener inside AbstractMethodInterceptor which provides invocation. Would you propose @leonard84 to pass the invocation instance to the listener in any simpler way?

@leonard84
Copy link
Member Author

@szpak, can you motivate this use case more? At first glance, I'd try to register a block listener that can handle all iterations instead of one per iteration.

Hmm, I think the main problem was to get the invocation instance to obtain the current field instance. As a result, I put IBlockListener inside AbstractMethodInterceptor which provides invocation. Would you propose @leonard84 to pass the invocation instance to the listener in any simpler way?

We could easily pass in the current instance. The question is whether we should.

I've also been debating whether I should give access to the current ISpockExecution so that a listener can get an IStore.

@szpak
Copy link
Member

szpak commented May 18, 2024

Hmm, I think the main problem was to get the invocation instance to obtain the current field instance. As a result, I put IBlockListener inside AbstractMethodInterceptor which provides invocation. Would you propose @leonard84 to pass the invocation instance to the listener in any simpler way?

We could easily pass in the current instance. The question is whether we should.

Definitely that's the good question. What are the design difference between interceptors (which have access to invocation) and listeners (also in work there are intended for)? The first could abort the processing, while listeners should not. Listeners should only perform (external) side effects? What also is different and if invocation in the second case could "complicate" something?

I've also been debating whether I should give access to the current ISpockExecution so that a listener can get an IStore.

Do you see any good cases where it would be necessary for listeners in practice? To read the values set by the other extensions/interceptors?

@szpak
Copy link
Member

szpak commented May 31, 2024

@szpak FWIW, you can probably get away with using a single instance of each and a ThreadLocal to share the data from the interceptor to the listener.

Trying to use ThreadLocal was failing with strange effect at first and in the end, it turned out that my original problem with "wrong iteration" was caused by the fact, I was not removing the block listener added in the iteration interceptor. As a result, in the second iteration 2 listeners were called (and checking the iteration index worked as a workaround)...

In the end, I can use just one block listener without any iteration index checking. Thanks.

https://github.com/szpak/spock-jpa-flush-enforcer/compare/preview2...preview3?expand=1

@leonard84
Copy link
Member Author

@szpak you should just register the blocklister once for the feature where you add the interationInterceptor instead of adding/removing it every time. Otherwise, you'll run into problems in parallel execution mode.

@szpak
Copy link
Member

szpak commented May 31, 2024

@szpak you should just register the blocklister once for the feature where you add the interationInterceptor instead of adding/removing it every time. Otherwise, you'll run into problems in parallel execution mode.

Thanks, fixed:
szpak/spock-jpa-flush-enforcer@bb830e1

@szpak
Copy link
Member

szpak commented Jun 3, 2024

Regarding GString in labels, I do not have a strong opinion. It might be useful for (more) fancy reporting, so some people might be delighted. On the other hand, I don't know how my (performance) impact it will have.

It would be a potential breaking change, although the impact should be negligible.

Some people might use it as a informal (and broken) placeholders. However, it should be easy to fix.

Btw, could we reuse BlockInfo if no GString is detected, and create a new instance only if an evaluation is needed?

@leonard84
Copy link
Member Author

Btw, could we reuse BlockInfo if no GString is detected, and create a new instance only if an evaluation is needed?

Maybe, with increased complexity.

@AndreasTu
Copy link
Member

I have no real preference in any way for or against the GString support.

How about we go with the existing behavior, and if someone wants the GString support in the future, we can still make the break?

@renatoathaydes
Copy link
Contributor

@renatoathaydes do you have any comments about the new listeners?

I am a bit out of the loop. But this sounds very useful to me.

This feature allows extension authors to register a IBlockListener for
a feature to observe the execution of a feature in more detail.
This surfaces some of Spock's idiosyncrasies, for example interaction
assertions are actually setup right before entering the preceding
`when`-block as well as being evaluated on leaving the `when`-block
before actually entering the `then`-block.

The only valid block description is a constant String, although some
users mistakenly try to use a dynamic GString. Using anything other
than a String, will be treated as a separate statement and thus ignored.
Prior to this commit, IRunListener.error(ErrorInfo) didn't give any
context where the error happened.
@leonard84 leonard84 force-pushed the add-block-listener-support branch 2 times, most recently from dcb9f79 to b819d74 Compare June 23, 2024 17:09
@leonard84
Copy link
Member Author

@renatoathaydes, please take a look at the new BlockListener and ErrorInfo interfaces.

I guess you are going to be one of the prime users of this feature, so if you have any comments, now would be the best time to address them.

@renatoathaydes
Copy link
Contributor

@leonard84 oh I see. I will try to checkout this code and get spock-reports to use the new feature, I suppose that will let me fix renatoathaydes/spock-reports#242 and maybe even renatoathaydes/spock-reports#89?

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.

Allow access to the currently executing block object
6 participants