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

CategoriesExcludeFilter and CategoriesIncludeFilter created. #635

Closed
wants to merge 4 commits into from
Closed

CategoriesExcludeFilter and CategoriesIncludeFilter created. #635

wants to merge 4 commits into from

Conversation

noel-yap
Copy link
Contributor

This is the first of at least a few changes that aim better to support Categories (eg creating the Request with the Filters is needed).

One open issue with this specific change is how best to specify from the command line the included and excluded categories. My current code uses the properties test.categories.include and test.categories.exclude. Are there other examples of JUnit using properties? Is there some naming convention to follow?

I'm sure there'll be lots of other questions, comments, etc (eg does JUnit already have support of Ant-style globbing that this change can just reuse?).

return fqn.replace(".", "/") + ".class";
}

public Class<?> forName(String className) throws ClassNotFoundException {
Copy link
Member

Choose a reason for hiding this comment

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

This appears to be unused

@dsaff
Copy link
Member

dsaff commented Feb 14, 2013

Can we tackle the command-line issue first?

I think what could work would be a general-purpose --filter command-line arg. For example, maybe:

java org.junit.runner.JUnitCore --filter=com.yap.noel.CategoryFilter=Foo*

Where there would need to be a public constructor CategoryFilter(String arg).

Thoughts?

@noel-yap
Copy link
Contributor Author

Just to clarify, with the --filter option applied to CategoriesIncludeFilter and CategoriesExcludeFilter, would it look something like: JUnitCore --filter='com.yap.noel.CategoriesIncludeFilter=/Category0.class,/Category1.class;com.yap.noel.CategoriesExcludeFilter=/Category2.class,/Category3.class'

@Tibor17
Copy link
Contributor

Tibor17 commented Feb 16, 2013

@dsaff
@stephenc
After 3 month there is no junit release for 4.12.

IMHO we should now postpone non-trivial pulls and release 4.12.

Then we should with @stephenc create multi-module maven project, and independently release junit:junit-experiments:4.13-SNAPSHOT for pulls like this one. Source code will be in junit-team in another basedir.
After the worldwide junit community and users accepted such snapshot, the last commit would become junit-extension release artifact.

@stephenc Did you think of multi module project?
I did and other people in other pull as well.
I vote for this necessary step.
I think we should do it because later we will have problems to split packages dependencies.

Tibor

@stephenc
Copy link
Contributor

Multi-module projects should not be split across git repositories. Seems you are suggesting the opposite.

Wrt cutting a release, I think it would be good to get a few maven based releases out (for practice) and the only thing I would like to fix is the project site now that junit.org is hosted on GitHub

Sent from my iPhone

On 16 Feb 2013, at 13:29, Tibor Digana [email protected] wrote:

@dsaff
@stephenc
After 3 month there is no junit release for 4.12.

IMHO we should now postpone non-trivial pulls and release 4.12.

Then we should with @stephenc create multi-module maven project, and independently release junit:junit-experiments:4.13-SNAPSHOT. Source code will be in junit-team in another basedir.
After the worldwide junit community and users accepted such snapshot, the last commit would become junit-extension release artifact.

@stephenc Did you think of multi module project?
I was and other people in other pull as well.
I vote for this necessary step.
I think we should do it because later we will have problems to split packages dependencies.

Tibor


Reply to this email directly or view it on GitHub.

@Tibor17
Copy link
Contributor

Tibor17 commented Feb 16, 2013

@stephenc
not to split across git repos.
I said a typical reactor with one aggregator pom and two modules in the beginning:

  • current junit:junit module as it is now , and
  • preliminary junit features module released very often as snapshot and dependent on junit:junit.

So the people can check out the preliminary features module, refactor them, and integrate back to junit:junit after they are really handy.
The same Git repo.

@stephenc
Copy link
Contributor

Differing release cycle => separate git repos.

On Saturday, 16 February 2013, Tibor Digana wrote:

@stephenc https://github.com/stephenc
not to split across git repos.
I said a typical reactor with one aggregator pom and two modules in the
beginning:

  • current junit:junit module as it is now , and

  • preliminary junit features module released very often as snapshot
    and dependent on junit:junit. So the people can check out the preliminary
    features module, refactor them, and integrate back to junit:junit after
    they are really handy. The same Git repo.


    Reply to this email directly or view it on GitHubhttps://github.com/CategoriesExcludeFilter and CategoriesIncludeFilter created. #635#issuecomment-13650964.

@noel-yap
Copy link
Contributor Author

I have no issues with not pulling in this change until after 4.12 is released.
On Feb 16, 2013 5:29 AM, "Tibor Digana" [email protected] wrote:

@dsaff https://github.com/dsaff
@stephenc https://github.com/stephenc
After 3 month there is no junit release for 4.12.

IMHO we should now postpone non-trivial pulls and release 4.12.

Then we should with @stephenc https://github.com/stephenc create
multi-module maven project, and independently release
junit:junit-experiments:4.13-SNAPSHOT. Source code will be in junit-teamin another basedir.
After the worldwide junit community and users accepted such snapshot, the
last commit would become junit-extension release artifact.

@stephenc https://github.com/stephenc Did you think of multi module
project?
I was and other people in other pull as well.
I vote for this necessary step.
I think we should do it because later we will have problems to split
packages dependencies.

Tibor


Reply to this email directly or view it on GitHubhttps://github.com//pull/635#issuecomment-13649668.

@kcooney
Copy link
Member

kcooney commented Feb 16, 2013

@stephenc @Tibor17 This pull request may not be the best place to discuss the release strategy of 4.12

public boolean shouldRun(final Description description) {
if (description.getMethodName() == null) {
if (description.getChildren().isEmpty()) {
final Category classCategory = description.getAnnotation(Category.class);
Copy link
Member

Choose a reason for hiding this comment

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

On the surface, it looks like this code only considers the Category annotations on the test methods, not the test classes.

Can we somehow reuse Categories.CategoriesFilter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm working on getting the other changes into this changeset that filter
the classes.

I'll take a look at CategoriesFilter again, but the first time I looked at
it, I didn't see a way to make it so what I wanted.
On Feb 16, 2013 8:42 AM, "Kevin Cooney" [email protected] wrote:

In src/main/java/org/junit/filters/CategoriesFilter.java:

@@ -0,0 +1,28 @@
+package org.junit.filters;
+
+import org.junit.experimental.categories.Category;
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+
+abstract class CategoriesFilter extends Filter {

  • public static final String DEFAULT = "";
  • @OverRide
  • public boolean shouldRun(final Description description) {
  •    if (description.getMethodName() == null) {
    
  •        if (description.getChildren().isEmpty()) {
    
  •            final Category classCategory = description.getAnnotation(Category.class);
    

On the surface, it looks like this code only considers the Categoryannotations on the test methods, not the test classes.

Can we somehow reuse Categories.CategoriesFilter?


Reply to this email directly or view it on GitHubhttps://github.com//pull/635/files#r3039689.

Copy link
Member

Choose a reason for hiding this comment

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

One possible way to reuse the code

  • Create the FilterFactory interface I suggested earlier
  • Have your FilterFactory implementation go through the Description tree to find all categories used by any of the test classes or methods that match your globs. It would then use that information to create an instance of Categories.FilterFactory and return that

That might require additional creational methods for Categories.FilterFactory. If so, it would be great to get that in soon.

My hope is we can get in the changes so that developers have the hooks they need to do command-line based filtering before the next major version, assuming the changes are small. Implementations of FilterFactory could be added in a later release or to junit.contrib

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kcooney , I just browsed through Categories.CategoryFilter and think it's way too complicated for what it needs to do -- it breaks the SRP. For example, there's no need to implement both categories include and exclude filtering in the same class. Rather, have one filter implement categories inclusion, another one implement categories exclusion, then use Filter.intersect() to combine both. Such a design makes it more amenable to combining other filters, too (eg an IgnoreFilter can be created that would obsolete specific @Ignore processing in JUnit (IIRC, such processing violates DRY since it occurs in at least two different places).

IMO (and I can surely be biased towards my own implementation :-), Categories.CategoryFilter ought to be deprecated in favor of CategoriesIncludeFilter and CategoriesExcludeFilter. I'm not opposed to making the latter two inner classes of Categories.

I'm already working on a 'FilterFactory' in order to support the --filter option (and possibly XML-based configurations if it's decided to move in that direction in the future). Such a factory was actually needed for our Ant task (especially since we do allow custom-filters to be used). (FWIW, I'd like to move as much of this functionality into JUnit proper since we're moving towards using Gradle for our builds and it looks like it's easier to (finally) contribute to JUnit rather than reimplementing something hacky in Gradle).

Copy link
Member

Choose a reason for hiding this comment

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

@noel-yap, agree in abstract principle that splitting responsibilities when possible is a good thing. That said, CategoryFilter is published, and deprecation has its costs. I don't see an argument above that convinces me that deprecating CategoryFilter would serve users in good stead--anyone who wants to implement inclusion in one object and exclusion in another can create two instances of CategoryFilter, and intersect them or do whatever else they wish.

@kcooney
Copy link
Member

kcooney commented Feb 16, 2013

@noel-yap Good to see you again!

The main thing that would help for me would be to have some integration tests. One way to do that:

  • Define a few category classes and test classes as nested classes of CategoriesIncludeFilterTest and CategoriesExcludeFilterTest (or perhaps a new CategoriesFilterTest)
  • Create a custom RunListener that records which tests were run and stores them in a Set<Description>
  • Use JUnitCore to run the tests with that listener
  • Verify that the expected tests are run

Edit: As a bonus, the integration tests would make it easier for us to come up with content for the wiki

There are probably examples in the code base that do something similar.

I'm particularly interested in:

  • Test classes where the test class has a @Category annotation, and some of the methods have additional @Category annotations
  • Category inheritance (ex: SmokeTests as a base class and FastTests as a subclass of SmokeTests and you want to include SmokeTests and expect tests annotated with FastTests to run)

If we all agree on how one will tell JUnitCore which filter to use via the command line, perhaps we can start with that change in a separate pull, and we can iterate on this pull in parallel.

@noel-yap
Copy link
Contributor Author

Good working with you again, too :-)

I'll work on these tests as part of the --filter processing.
On Feb 16, 2013 8:49 AM, "Kevin Cooney" [email protected] wrote:

@noel-yap https://github.com/noel-yap Good to see you again!

The main thing that would help for me would be to have some integration
tests. One way to do that:

  • Define a few category classes and test classes as nested classes of
    CategoriesIncludeFilterTest and CategoriesExcludeFilterTest (or
    perhaps a new CategoriesFilterTest)
  • Create a custom RunListener that records which tests were run and
    stores them in a Set
  • Use JUnitCore to run the tests with that listener
  • Verify that the expected tests are run

There are probably examples in the code base that do something similar.

I'm particularly interested in:

  • Test classes where the test class has a @category annotation, and
    some of the methods have additional @category annotations
  • Category inheritance (ex: SmokeTests as a base class and FastTestsas a subclass of
    SmokeTests and you want to include SmokeTests and expect tests
    annotated with FastTests to run)

If we all agree on how one will tell JUnitCore which filter to use via
the command line, perhaps we can start with that change in a separate pull,
and we can iterate on this pull in parallel.


Reply to this email directly or view it on GitHubhttps://github.com//pull/635#issuecomment-13657915.

@kcooney
Copy link
Member

kcooney commented Feb 16, 2013

@noel-yap It might be nice to know what changes we might need to Categories before 4.12 is released to make it easier for you to do what you want without another JUnit release (for example, simple changes to the filter).

@dsaff The problem with requiring the filter to have a constructor that takes in a String is it might require the developer to do a lot of "work" in the constructor (we commonly see this problem at my job when we write custom Runners). I suggest a factory interface:

public interface FilterFactory {
  Filter createFilter(Description topLevelDescription, String arg);
}

The reason why I would like to have the top level description object passed in is there are times when you cannot define the filter without knowing what the tree looks like.

If you are worried about developers wanting more parameters in the future, we could pass in a FilterFactoryParams class and have that class created with a builder. On the surface, that seems overkill here, but we did modify what parameters could be passed to the Runner constructor.

@noel-yap
Copy link
Contributor Author

I'd like to say I'll have something next week, but I'm planning to be OOO
at least part of the week so I can't make any promises. What's the timeline
for the 4.12 release?
On Feb 16, 2013 9:08 AM, "Kevin Cooney" [email protected] wrote:

@noel-yap https://github.com/noel-yap It might be nice to know what
changes we might need to Categories before 4.12 is released to make it
easier for you to do what you want without another JUnit release (for
example, simple changes to the filter).

@dsaff https://github.com/dsaff The problem with requiring the filter
to have a constructor that takes in a String is it might require the
developer to do a lot of "work" in the constructor (we commonly see this
problem at my job when we write custom Runners). I suggest a factory
interface:

public interface FilterFactory {
Filter createFilter(Description topLevelDescription, String arg);
}

The reason why I would like to have the top level description object
passed in is there are times when you cannot define the filter without
knowing what the tree looks like.

If you are worried about developers wanting more parameters in the future,
we could pass in a FilterFactoryParams class and have that class created
with a builder. On the surface, that seems overkill here, but we did modify
what parameters could be passed to the Runner constructor.


Reply to this email directly or view it on GitHubhttps://github.com//pull/635#issuecomment-13659158.

@Tibor17
Copy link
Contributor

Tibor17 commented Feb 16, 2013

@noel-yap
We had these category properties in #503 and they were able to merge with annotated included/excluded categories in the suites. All tests passed, but for simplicity reasons we refused and keep only annotations.

Regarding the command line.
One syntax might be useful for you but not for others. Others may prefer XML like TestNG has.
We already had a discussion like this. We had arguments to console specific characters. I think also the ".class" in command line which is ugly.
So therefore I prefer only API built on the top of CategoryFilter, and not the command patter.

I do not want to make you disapointed, but command line staff was discussed.
I still think that JUnit should be like Hamcrest which has many modules, and I can see this request in a layer on the top of junit core. Because the JUnit core is programmatic API, other modules should have own specifics. At least this is my vision.

@noel-yap
Copy link
Contributor Author

@Tibor17 , perhaps I misunderstand, but it looks to me as though my pull request is complementary to what's discussed in #503. Adding --filter would allow any number of perhaps-custom filters to be used in JUnit. Command line support doesn't mean XML-based configuration ought not be considered, either.

I'm not opposed to leaving out the '.class' in the filter and categories specifications. The only reason it's there is to maintain consistency with how our original build system filtered in and out classes (ie by using Ant globbing). I'm even open to removing the Ant globbing altogether and having the user specify FQCNs (but that could be a pain if ever such classes were refactored into other packages).

The important parts for me is to have some easy way to specify filter configuration on the command line and for tests that have been filtered out to call fireTestIgnored() (so we can track which tests are being skipped).

@Tibor17
Copy link
Contributor

Tibor17 commented Feb 17, 2013

@noel-yap
I mentioned props in #503 because we tried to configure something similar, categories classes through properties.
And you asked for props conventions in the pull description.
So the properties for defining FQCN of a filter is still not satiable ?

I tried to explain to you that the JUnit has the tendency to lead the style of programmatic API.
The textual soft coding or configuration is out of this scope -same for xml, etc.

@noel-yap
Copy link
Contributor Author

@Tibor17 , sorry, I'm new to github and am having trouble finding the specifics about the properties you mentioned.

@Tibor17
Copy link
Contributor

Tibor17 commented Feb 18, 2013

@noel-yap
Would this solve your problem?
-Dorg.junit.included.filter=com.yap.noel.IncCategoryFilter
-Dorg.junit.excluded.filter=com.yap.noel.ExcCategoryFilter
without command line?
It's not usual that build process uses command line, right?
Usually builds are automatically triggered by some event like time, or submitted change list in VCS.
This is configuration of the b.process via system properties (-D).
So you can simply start tests with your filter in the java process:
java -Dorg.junit.included.filter=com.yap.noel.IncCategoryFilter org.junit.runner.JUnitCore com.yap.MyTest1 com.yap.MyTest2

@dsaff
Copy link
Member

dsaff commented Feb 18, 2013

Catching up here.

@kcooney, FilterFactory is a strictly better suggestion than my magic-constructor proposal. Thank you.

@noel-yap, we run the danger that by describing this pull request "better to support Categories", and starting off with code that doesn't actually intersect with the currently-running code, we've created a bit of a mirror into which each of us can project our own desires about what the obvious next thing to do with Categories is. At this point, I'm personally most interested in the possibility of creating a standard command-line filter parameter. This has several advantages:

  1. It avoids having specific properties or args that are meant to be processed by only one filter or runner needing to be piped through code that doesn't care about them (or worse, stuck in some global registry).
  2. It should be possible to implement it with a relatively small edit to JUnitCore, leaving the rest of the code unchanged.
  3. It would create a standard that IDE's and build tools could follow.

@Tibor17, see above for why I am interested in a general filter parameter, but remain uninterested (as I was in #503) in introducing special-case properties or args just for categories.

The conversation thus far has not convinced me that there's anything fundamentally wrong with CategoryFilter that justifies replacing, vs. fixing it. Once we have a general command-line filtering mechanism in place, things like ant-globbing can become specific to a particular filter implementation, and can be discussed in a smaller scope.

@noel-yap
Copy link
Contributor Author

@dsaff , how about something like: --filter='callable0(arg0, arg1, ..., argN)|callable1(arg0, arg1, ..., argN)|...|callableN' where callable0...callableN are fully qualified constructors or static functions and arg0...argN are passed in as Strings? Examples:

  • --filter=org.junit.experimental.categories.Categories.CategoryFilter.include(com.yap.noel.Category0)|org.junit.experimental.categories.exclude(com.yap.noel.Category1)
  • --filter=org.junit.experimental.categories.Categories.CategoryFilter(true, com.yap.noel.Category0, true, com.yap.noel.Category1)

JUnitCore.setFilter() can also be defined in the API for those not wanting to go through the command line. While direct support for properties won't be implemented, custom filters can use properties if they want.

@dsaff
Copy link
Member

dsaff commented Feb 20, 2013

@noel-yap, I like setFilter(). I was hoping we could combine filters by including multiple --filter args on the command line, but perhaps that raises the spectre of whether multiple filters are intersected or unioned.

My gut feeling is that single class name + string arg will be perfectly sufficient, and I'd like to draw the line long before we risk maintaining a complicated DSL, with all of the parsing and escaping issues that raises.

@noel-yap
Copy link
Contributor Author

@dsaff , multiple --filter has the advantage of being easier to assemble than using infix '|' syntax. Let's go with that. It would mean renaming setFilter to `addFilter'.

I'm not sure how useful filter union would be. Even if someone wanted to do that, they could just call JUnit multiple times.

Single class name with one String arg is fine. I think allowing static function names with a single String arg would be helpful, too, in that it would help eliminate a proliferation of classes. Do you see any reasons why that ight not be wanted?

@dsaff
Copy link
Member

dsaff commented Feb 20, 2013

@noel-yap, how about we get the factory class case built, as a minimum viable feature, and then consider the static method proposal as an extension?

@kcooney
Copy link
Member

kcooney commented Feb 20, 2013

@noel-yap To be clear, are these command line parameters or system
properties? We keep saying the latter but the examples use the former.

Note that if we use system properties, you could choose to use system
properties to configure your filter:

-Dorg.junit.filter=
com.netfix.testing.Categories
-Dcom.netflix.testing.include=
**/FastTests
On Feb 20, 2013 11:27 AM, "Noel Yap" [email protected] wrote:

@dsaff https://github.com/dsaff , how about something like: --filter='callable0(arg0,
arg1, ..., argN)|callable1(arg0, arg1, ..., argN)|...|callableN' where
callable0...callableN are fully qualified constructors or static
functions and arg0...argN are passed in as Strings? Examples:

--filter=org.junit.experimental.categories.Categories.CategoryFilter.include(com.yap.noel.Category0)|org.junit.experimental.categories.exclude(com.yap.noel.Category1)

  • --filter=org.junit.experimental.categories.Categories.CategoryFilter(true,
    com.yap.noel.Category0, true, com.yap.noel.Category1)

JUnitCore.setFilter() can also be defined in the API for those not wanting
to go through the command line. While direct support for properties won't
be implemented, custom filters can use properties if they want.


Reply to this email directly or view it on GitHubhttps://github.com//pull/635#issuecomment-13847844.

@dsaff
Copy link
Member

dsaff commented Feb 21, 2013

@kcooney, I'd like to use command-line parameters first: system properties are inherently global in ways that I'd like to avoid, but we could revisit that once we have a syntax we like.

@noel-yap
Copy link
Contributor Author

@dsaff , since we're talking about accepting only constructors that take in only one (or no) String arguments, I'm going back to the '=' syntax: --filter=fqcn0=arg --filter=fqcn1.

I expect to have something by EOD tomorrow or early next week.

JUnitCore.addFilter() created.
Filtered out methods and classes treated as if they were @ignored.
@noel-yap
Copy link
Contributor Author

I think the messiest part of the latest changeset is the FilteredClassRunner decorator. The functionality in its runChild() could be moved directly into BlockJUnit4ClassRunner and its existing subclasses that override runChild() but I wasn't sure if that would have other consequences.

… the Ignore annotation handling into a more general filter handling; the specific Ignore annotation handling is replaced with an IgnoreFilter instance.
@noel-yap
Copy link
Contributor Author

It turns out that moving the FilteredClassRunner.runChild() logic into BlockJUnit4ClassRunner wasn't so bad.

@kcooney
Copy link
Member

kcooney commented Feb 27, 2013

Looks like in github when you comment on an earlier commit, it's really hard to do a reply, so I'll summarize

  1. I think we should remove the changes to the way @Ignore is handled and do that in another pull. I have doubts that we want to use filtering to handle @Ignore because a) filtered tests do not show up in the Description tree, while ignored tests do, b) there is a notification for ignored tests, but not filtered tests, c) the current implementation doesn't use Filterable
  2. We shouldn't need the changes to Request or AllDefaultPossibilitiesRunnerBuilder. You can have JUnitCore create the request from the classes, create the filter(s), then tell the request to filter itself. I fixed a number of bugs in 4.9 (?) related to filtering; I'm hesitant to make major changes to the implementation.
  3. I'd like to have users create a factory that creates the filter instead of creating a filter that has to have a specific set of arguments in the constructor.

system.out().println("JUnit version " + Version.id());
List<Class<?>> classes = new ArrayList<Class<?>>();
List<Failure> missingClasses = new ArrayList<Failure>();
List<Failure> failures = new ArrayList<Failure>();
FilterFactory filterFactory = new FilterFactory();
for (String each : args) {
Copy link
Member

Choose a reason for hiding this comment

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

Could we extract a class to do the command line parsing?

CommandLineParser parser = new CommandLineParser(args);
parser.parse();
List<Class<?>> classes = parser.getClasses();
List<Failure> failures = parser.getFailures();
List<FilterFactory> filterFactories = parser.getFilterFactories();

@noel-yap
Copy link
Contributor Author

@kcooney , what is the advantage of keeping @ignore and filtering separate such that filtering receives no notifications? The advantage I see of combining the two is simplification; there'll be fewer concepts floating around; @ignore handling will simply be just another Filter.

FWIW, my group has created a custom Ant task to use ignore notifications for filtered tests so that we know which tests are being skipped. These tests are currently being skipped in CI because they've been Categorized as Manual or DevMachine. This allows the tests to be run locally while being skipped on CI. @ignore doesn't allow this capability.

We're going to be switching over to Gradle and was hoping I could move the above functionality into JUnit itself rather than hacking yet another build system to do what we want. I'm thinking the gains in putting it into JUnit would be greater than the gains in putting it into Gradle.

Perhaps the proper solution would be to introduce something new, something like a NotifiedFilter? Or maybe there ought to be some option (somewhere) that indicates whether Filters perform notification when a test is skipped?

I've started a separate thread for this topic.

@dsaff dsaff closed this Feb 27, 2013
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.

5 participants