-
Notifications
You must be signed in to change notification settings - Fork 300
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 support to test caught exceptions (fixes #591) #813
Conversation
|
||
@Override | ||
public CaughtThrowable apply(JavaClassDescriptor input) { | ||
return new CaughtThrowable(input); |
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.
In this minimal version, only the exception type is retained in CaughtThrowable
This is a pretty large PR as it is, I'd suggest that any enhancements be added in a separate PR
AccessRecord<ConstructorCallTarget> create(RawAccessRecord record, ImportedClasses classes) { | ||
return new RawAccessRecordProcessed<>(record, classes, CONSTRUCTOR_CALL_TARGET_FACTORY); | ||
ConstructorCallRecord create(RawAccessRecord.ForMethodCall record, ImportedClasses classes) { | ||
return new RawConstructorCallRecordProcessed(record, classes); |
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.
Quite a lot of layers to punch through :)
I didn't quite get the idea behind RawAccessRecord
/RawXXXRecordProcessed
. Am I wrapping JavaClassDescriptor
into CaughtThrowable
in the right layer?
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 can explain the idea quickly: RawAccessRecord
represents the raw data as collected during bytecode analysis (i.e. origin and target are just strings or type descriptors, etc.). RawAccessRecordProcessed
represents the next step, when this raw data has been resolved to domain objects (e.g. the target is now not a string anymore, but a JavaMethodCallTarget
which has a JavaClass
as its owner, the origin is a JavaCodeUnit
, etc.).
JavaClasses classes = new ClassFileImporter().importUrl(getClass().getResource("testexamples/trycatch")); | ||
JavaClass classHoldingMethods = classes.get(ClassHoldingMethods.class); | ||
JavaClass classWithTryCatchBlocks = classes.get(ClassWithTryCatchBlocks.class); | ||
JavaMethod setSomeInt = classHoldingMethods.getMethod("setSomeInt", int.class); |
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 wish there was some metamodel tool to generate those automatically :|
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.
Well, the user doesn't have one either 😉
Thank you so much for all your hard work!! 😃 I'm really sorry, but I feel we should have discussed it more ahead though, because now looking deeper into it there are a lot of questions coming to my mind where things don't seem as clear as I thought on first sight 🤔 Because somehow the discussion how to model this just stopped and we never really came to a final conclusion (I just threw in that I would prefer But still, let's start discussing this a little though, because as I said there are some basic points for me that influence everything. The most important one for me is the model. @hankem was asking if it should be something like The current model in the PR is very close to the issue, i.e. the concrete issue would be super easy to solve with this model. But I'm not sure it's the best domain model in general. Because, if for example I would want to write a rule on all the caught exception types. Then I would now have to track down every single call, gather all the exception types and deduplicate, because multiple calls in the same block all have the same I'm wondering if the following wouldn't be a more intuitive model:
This model would make it a little more tedious to solve the concrete issue (I would now have to check all the try-catch-blocks to see if the relevant call is contained and the caught throwables match), but to me it feels better as a generic model to also implement other rules users might have for try-catch-blocks 🤔 (but I might be wrong 🤷♂️) But it would also be possible to e.g. add What do you think about these points? |
@codecholeric Your comment raises a couple of different points, so let me try and unpack it step by step.
I had a hard time coming up with an appropriate name, and I agree
I believe we are discussing two separate use cases. My PR focused on covering @cpollet's use case and would not be easily solved by your proposal. Your use case, in turn, also sounds completely valid and the call for having Perhaps then, our proposed solutions are both correct, and the association between What I would suggest now, is to keep this solution as it is, and add another PR that would in addition create a
I'd rather not rely on line numbers here, since both try and catch blocks (and even multiple EDIT Just to be clear, I'm proposing: class TryCatchBlock {
int tryBlockStartLineNumber;
int tryBlockEndLineNumber;
Set<CaughtThrowable> caughtThrowableTypes;
Set<JavaCall> javaCalls;
}
class JavaCodeUnit {
// ...
Set<TryCatchBlock> tryCatchBlocks;
}
class JavaCall {
Set<CaughtThrowable> throwablesHandledByCaller; // or perhaps just Throwable?
} WDYT? |
Yes, a bidirectional association was what I meant by adding I fully agree about your argument with the line numbers, it would likely work most of the time, but it would be shady. Because in theory you can also do crazy one-line things like this:
Maybe the line numbers of start and end could still be interesting, but I think we can leave them out and see if users really want them 👍 Coming back to how to model the types, as mentioned, I think one domain object would be good enough. I think the best would be to record the necessary info (like labels, etc.) on import, then construct
This would offer a convenient API. (any better name than One thing I'm wondering about is if we should just make it |
For conditions based on
Shouldn't this P.S. I'm very sorry that #591 (comment) might have led in a wrong direction! |
True, it could implement |
Also no worries about the comment @hankem, you only asked a question and we never finished the discussion. I should probably be more careful about slapping the |
BTW @crizzis, we have some code style files for IntelliJ and Eclipse checked in at https://github.com/TNG/ArchUnit/tree/main/develop. You can use those to make sure the formatting is consistent and the imports don't change unnecessarily! |
@codecholeric Fixed the code style, converted Is it okay with you for the (Also, I guess it makes sense for |
50dc02e
to
b4d1356
Compare
Sorry that I haven't gotten back to you on this for so long!! I read through the code, then decided I should support you more (since obviously this whole back and forth wasn't super motivating for you, sorry about that), then got sick for a while 😬 Anyway, I invested quite some time now to pretty much bend it into the direction of having try-catch-blocks on the First of all the code was very nice to read 👍 😃 And also respect that you managed to bite through all those layers, understand and handle the ASM visitor API and then implement it correctly, that was certainly not easy! I'll try to provide some constructive feedback about things I noticed:
In any case, I would be happy if you could look at my proposed changes and tell me what you think about them! Maybe it can also be made clearer in some parts or written more efficiently. I'm in particular not super happy that we now have to pass the |
No worries, life happens, hope things are better now ;) Unfortunately, life happens to me too, and while I promise to have a look at the suggestions, what I can't promise is that it will happen anytime soon, as I will have much less time now to contribute to open source stuff. The ultimate goal is of course to ship new, shiny functionality to users, and most of the proposed changes sound very reasonable, so if you feel the feature can't wait, I wouldn't like to make my lack of time a showstopper. I really appreciate the constructive feedback, and I can always revisit the PR even after it is merged, the code is not set in stone, after all. Otherwise I will have to ask you for some patience, but either way, it's probably not a good idea to leave a PR alive for too long lest it becomes hopelessly conflicted with the main branch. I'm leaving the decision up to you. Cheers! |
Okay, don't stress about it! I still have to look at your other PR as well, which I couldn't find the time yet 🙈 |
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 haven't yet played with the new feature for myself, and I didn't understand the one test with the 3 try-catch-blocks, but other than that, the code looks good to me!
@@ -92,7 +92,7 @@ public static List<String> formatNamesOf(Class<?>... paramTypes) { | |||
* @return A {@link List} of fully qualified class names of the passed {@link Class} objects | |||
*/ | |||
@PublicAPI(usage = ACCESS) | |||
public static List<String> formatNamesOf(Iterable<Class<?>> paramTypes) { | |||
public static List<String> formatNamesOf(Iterable<? extends Class<?>> paramTypes) { |
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.
@codecholeric, why would we need ? extends Class
?
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.
That is part of the Java Generics variance behavior. At least AFAIS without the ? extends
you can't use it for
Formatters.formatNamesOf(ImmutableList.of(String.class, Serializable.class));
while you can use it for
Formatters.formatNamesOf(ImmutableList.of(String.class, Object.class));
The problem seems to be that in the former case the inferred type is List<Class<? extends Serializable>>
which seems to conflict with Iterable<Class<?>>
. So to make the API more convenient I changed the signature to allow subtypes of Class<?>
, because then the former case compiles without the need to explicitly fix the type by ImmutableList.<Class<?>>
. Should maybe add a test though and split out the commit 😉 (because I saw that the final state actually compiles without this change, so I think I added it at some point when I stumbled over it, but the reason afterwards wasn't necessary to be added to the PR after all 🤷♂️)
archunit/src/main/java/com/tngtech/archunit/core/domain/TryCatchBlock.java
Outdated
Show resolved
Hide resolved
archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java
Show resolved
Hide resolved
archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java
Outdated
Show resolved
Hide resolved
archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java
Show resolved
Hide resolved
archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java
Outdated
Show resolved
Hide resolved
archunit/src/test/java/com/tngtech/archunit/testutil/assertion/CodeUnitAccessAssertion.java
Outdated
Show resolved
Hide resolved
archunit/src/test/java/com/tngtech/archunit/testutil/assertion/CodeUnitAccessAssertion.java
Outdated
Show resolved
Hide resolved
archunit/src/test/java/com/tngtech/archunit/testutil/JavaCallQuery.java
Outdated
Show resolved
Hide resolved
archunit/src/test/java/com/tngtech/archunit/testutil/JavaCallQuery.java
Outdated
Show resolved
Hide resolved
At the moment if there is some sort of `Collection<Class<? extends SomeBound>>` instead of `Collection<Class<?>>` then `Formatters.formatNamesOf(collection)` does not compile, because `Class<? extends SomeBound>` counts as subtype of `Class<?>`. Thus, we change the signature to allow `? extends Class<?>` to make this work out of the box with bounded types. Signed-off-by: Peter Gafert <peter.gafert@tngtech.com>
It is more consistent if all concrete assertion classes reside in `testutil.assertion`, instead of just some of them. Signed-off-by: Peter Gafert <peter.gafert@tngtech.com>
This will add `TryCatchBlocks` to the ArchUnit core and introduce `JavaCodeUnit.getTryCatchBlocks()` to examine try-catch blocks that have been parsed from the bytecode of a method or constructor. We also add an extension `JavaAccess.getContainingTryBlocks()` to make it easy to verify that certain accesses in code are wrapped into certain try-catch blocks (e.g. "whenever method x is called there should be a try-catch block to handle exception case y"). Signed-off-by: Krzysztof Sierszeń <krzysztof.sierszen@digitalnewagency.com> Signed-off-by: Peter Gafert <peter.gafert@tngtech.com>
This will add
TryCatchBlocks
to the ArchUnit core and introduceJavaCodeUnit.getTryCatchBlocks()
to examine try-catch blocks that have been parsed from the bytecode of a method or constructor. We also add an extensionJavaAccess.getContainingTryBlocks()
to make it easy to verify that certain accesses in code are wrapped into certain try-catch blocks (e.g. "whenever method x is called there should be a try-catch block to handle exception case y").Resolves: #591
Signed-off-by: Krzysztof Sierszeń krzysztof.sierszen@digitalnewagency.com