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

Contextualise CF tck #197

Merged
merged 2 commits into from
Sep 2, 2020
Merged

Conversation

FroMage
Copy link
Contributor

@FroMage FroMage commented Aug 18, 2020

This is the TCK part of #173 whose code PR was merged.

I've added tests for the following things:

  • ManagedExecutor.copy(CF/CS) will propagate contexts
  • ManagedExecutor.getThreadContext() will return an instance with the same propagation settings as the ManagedExecutor
  • ManagedExecutor.newIncompleteFuture() (hoping all other methods behave the same, I didn't think it was worth testing them all, should I?) will respect the new ContextManager.Builder.withDefaultExecutorService()
  • ThreadContext.withContextPropagation(CF/CS) will respect the new ContextManager.Builder.withDefaultExecutorService(), and ban Async methods with no executor if not set, and allow them if set

Did I forget something?

Copy link
Contributor

@njr-11 njr-11 left a comment

Choose a reason for hiding this comment

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

I tried out the TCK test on OpenLiberty's implementation, and there are a couple of problems, although after I made adjustments to the TCK locally, everything passed.

First, ManagedExecutorTest and ThreadContextTest are getting the following error:

java.lang.NoClassDefFoundError: org.eclipse.microprofile.context.tck.TckThreadFactory

This is because the test case uses the new TckThreadFactory class, but the class isn't part of the test application. It is easily fixed by adding,

in ManagedExecutorTest:

        return ShrinkWrap.create(WebArchive.class, ManagedExecutorTest.class.getSimpleName() + ".war")
                .addClass(ManagedExecutorTest.class)
                .addClass(org.eclipse.microprofile.context.tck.TckThread.class)
                .addClass(org.eclipse.microprofile.context.tck.TckThreadFactory.class)
                .addAsLibraries(fakeContextProviders);

and in ThreadContextTest:

        return ShrinkWrap.create(WebArchive.class, ThreadContextTest.class.getSimpleName() + ".war")
                .addClass(ThreadContextTest.class)
                .addClass(ThreadContextTest.class)
                .addClass(org.eclipse.microprofile.context.tck.TckThread.class)
                .addClass(org.eclipse.microprofile.context.tck.TckThreadFactory.class)
                .addAsLibraries(threadPriorityContextProvider, multiContextProvider);

After fixing the previous error, I see 3 occurrences of the following, which is due to the TCK requiring the optional ContextManagerProvider.getContextManagerBuilder method,

java.lang.UnsupportedOperationException
at org.eclipse.microprofile.context.spi.ContextManagerProvider.getContextManagerBuilder(ContextManagerProvider.java:155)
at org.eclipse.microprofile.context.tck.ManagedExecutorTest.withDefaultExecutorServiceIsUsedDirectlyAndViaGetThreadContext(ManagedExecutorTest.java

I made a note of this in each place. The UnsupportedOperationException just needs to be caught and the test skipped because the method is optional.

.getContextManagerBuilder()
.withDefaultExecutorService(executorService)
.addDiscoveredThreadContextProviders()
.build();
Copy link
Contributor

Choose a reason for hiding this comment

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

ContextManagerProvider.getContextManagerBuilder() is optional. We need to allow for implementations that don't support it. I believe this can be done as follows:

        ContextManager.Builder contextManagerBuilder;
        try {
            contextManagerBuilder = ContextManagerProvider.instance().getContextManagerBuilder();
        } catch (UnsupportedOperationException x) {
            // getContextManagerBuilder is optional. Skip if unimplemented.
            return;
        }
        ContextManager contextManager = contextManagerBuilder
                .addDiscoveredThreadContextProviders()
                .withDefaultExecutorService(null)
                .build();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ouch, I didn't realise that. But then how can I test the executor service in this case? I need to make sure that it works if it's specified and if it's set to null. If I use the default context manager I can't know.

Copy link
Contributor

Choose a reason for hiding this comment

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

If an implementation doesn't support getContextManagerBuilder, that means there is no way to configure the default executor service. The implementation will either have a built-in default that it uses for async dependent stages of withContextCapture, or it won't have a built-in default, which will mean UnsupportedOperationException is appropriate when attempting to create async stages. In order to test this, we can trap for UnsupportedOperationException on thenApplyAsync(fn), which is considered a passing result when getContextManagerBuilder is rejected, and in the absence of the exception, check that async dependent stages that are created from the copied stage run successfully, independent of any constraints that might be in place on the executor of the stage from which it was originally copied.

Copy link
Contributor

Choose a reason for hiding this comment

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

To clarify, your test is fine, except that it needs to trap UnsupportedOperationException and skip itself in that case. And then a separate test can be written to cover the default context manager, in which the test will need to trap UnsupportedOperationException on thenApplyAsync (or whatever *Async method is used)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, fine, is this better then?

Copy link
Contributor

Choose a reason for hiding this comment

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

Mostly, but two other issues were introduced in the changes. I added comments for those inline instead of under this conversation.

ExecutorService executorService = Executors.newSingleThreadExecutor(new TckThreadFactory());
ContextManager contextManager = ContextManagerProvider
.instance()
.getContextManagerBuilder()
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as previous comment - getContextManagerBuilder is optional, so test needs to skip if not implemented.

throws ExecutionException, InterruptedException, TimeoutException {
ContextManager contextManager = ContextManagerProvider
.instance()
.getContextManagerBuilder()
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as previous comment - getContextManagerBuilder is optional, so test needs to skip if not implemented.

@FroMage FroMage force-pushed the contextualise-cf-tck branch 2 times, most recently from af04c29 to a28ecd0 Compare August 21, 2020 15:00
Copy link
Contributor

@njr-11 njr-11 left a comment

Choose a reason for hiding this comment

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

See inline comments for remaining 2 issues that were introduced in the fix. Also one minor comment that I forgot to mention yesterday - the copyright year should include 2020 on files.

public void withDefaultExecutorServiceIsUsedDirectlyAndViaGetThreadContext()
throws ExecutionException, InterruptedException, TimeoutException {
ExecutorService executorService = Executors.newSingleThreadExecutor(new TckThreadFactory());
ContextManager contextManager = ThreadContextTest.getContextManagerBuilderIfSupported()
Copy link
Contributor

Choose a reason for hiding this comment

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

This fails with

java.lang.NoClassDefFoundError: org.eclipse.microprofile.context.tck.ThreadContextTest

To fix, just need to add ThreadContextTest.class under addClasses

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ooops, done.

.build();
} catch (SkipException x) {
// not supported, use the default and assume that one does not have a default executor service
contextManager = ContextManagerProvider.instance().getContextManager();
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not a valid assumption. OpenLiberty's default/built-in context manager does have a default executor service. There is nothing in the spec that would prohibit this and force the assumption to hold, and so the test cannot require it. The Assert.fail lines that follow should be replaced with checking that the async stage actually does complete successfully.

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 not following. I thought the behaviour of ThreadContext.withContextCapture(CompletionStage) used to be that *Async methods should throw, and should keep on throwing if we can't specify a default executor.

Are you saying that OpenLiberty will make them not throw now?

If that is the case, then I can't check at all whether the Async methods throw or not with OpenLiberty because I can't predict whether or not there will be a default executor and so both options would be fine depending on implementation, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not following. I thought the behaviour of ThreadContext.withContextCapture(CompletionStage) used to be that *Async methods should throw

You are correct about what the requirement used to be.

and should keep on throwing if we can't specify a default executor.

The JavaDoc isn't worded that way. It states that apart from the ManagedExecutor.getThreadContext path, the new completion stages will use "the default executor service if provided by the platform".

Are you saying that OpenLiberty will make them not throw now?

Yes. OpenLiberty does provide a default executor service and therefore uses it here. The default executor service happens to not be switchable, but that is beside the point. While it would be possible to change the spec to prohibit this, I cannot see why we would change it to prohibit because allowing customers to use the *Async methods is the most useful behavior.

If that is the case, then I can't check at all whether the Async methods throw or not with OpenLiberty because I can't predict whether or not there will be a default executor and so both options would be fine depending on implementation, right?

Correct. Both options are valid for implementations, so a test needs to consider both to be passing results. It's still worth keeping the test because it will verify there are no failures at other points.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, thanks. Now I get it. I've updated the test.

.getContextManagerBuilder()
.withDefaultExecutorService(executorService)
.addDiscoveredThreadContextProviders()
.build();
Copy link
Contributor

Choose a reason for hiding this comment

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

Mostly, but two other issues were introduced in the changes. I added comments for those inline instead of under this conversation.

@FroMage FroMage force-pushed the contextualise-cf-tck branch from a28ecd0 to cf3db1e Compare August 24, 2020 12:08
ManagedExecutor.copy
ManagedExecutor.getThreadContext
ContextManager.Builder.withDefaultExecutorService
…t run async actions without executor

Because it really depends if there is a default executor, and we have tests for both cases elsewhere (with/without)
@FroMage FroMage force-pushed the contextualise-cf-tck branch from cf3db1e to dcdeb54 Compare August 25, 2020 08:17
Copy link
Contributor

@njr-11 njr-11 left a comment

Choose a reason for hiding this comment

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

Looks good, and the new tests are all passing now.

@FroMage
Copy link
Contributor Author

FroMage commented Sep 1, 2020

Shall I merge it then?

@njr-11
Copy link
Contributor

njr-11 commented Sep 1, 2020

Shall I merge it then?

It all looks good to me and I'm in favor of merging. It looks like you also have @manovotn listed as a reviewer.

@FroMage
Copy link
Contributor Author

FroMage commented Sep 1, 2020

We can wait for @manovotn

Copy link
Contributor

@manovotn manovotn left a comment

Choose a reason for hiding this comment

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

Sorry, just forgot to green-stamp this one.

@FroMage FroMage merged commit 1098b57 into eclipse:master Sep 2, 2020
@FroMage
Copy link
Contributor Author

FroMage commented Sep 2, 2020

Thanks.

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.

3 participants