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

Document how to use CloseableResource #1555

Closed
1 task
sbrannen opened this issue Aug 19, 2018 · 17 comments · Fixed by #3840
Closed
1 task

Document how to use CloseableResource #1555

sbrannen opened this issue Aug 19, 2018 · 17 comments · Fixed by #3840

Comments

@sbrannen
Copy link
Member

Overview

As discussed in the Gitter channel, users would greatly benefit from detailed documentation and examples regarding how to make use of CloseableResource in the ExtentionContext.Store.

Deliverables

  • Document how to use CloseableResource in various scenarios, potentially via examples in the junit5-samples repository.
@vyarosh
Copy link

vyarosh commented Aug 20, 2018

This is my use case: create html report object before all tests
and write to file after all tests executed:

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class AfterTestExecutionHook implements
        BeforeAllCallback {

    @Override
    public void beforeAll(ExtensionContext context) throws Exception {
        //initialize "after all test run hook"
        context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL).put("my_report", new CloseableOnlyOnceResource());
    }

    private static class CloseableOnlyOnceResource implements
            ExtensionContext.Store.CloseableResource {
        //ReportManager contains static field of MyReport type as singleton storage and access point  
        private static MyReport htmlReport = ReportManager.createReportSingleton();

        @Override
        public void close() {
            //After all tests run hook.
            //Any additional desired action goes here 
            htmlReport.write();
        }
    }
}

usage:

@ExtendWith(AfterTestExecutionHook .class)
class AnotherClassDemo {

@sormuras
Copy link
Member

sormuras commented Aug 21, 2018

Here is also a unit test showing a global resource:

public class Heavyweight implements ParameterResolver, BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
context.getStore(ExtensionContext.Namespace.GLOBAL).put("once", new CloseableOnlyOnceResource());
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext context) {
return Resource.class.equals(parameterContext.getParameter().getType());
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context) {
ExtensionContext engineContext = context.getRoot();
Store store = engineContext.getStore(ExtensionContext.Namespace.GLOBAL);
ResourceValue resource = store.getOrComputeIfAbsent(ResourceValue.class);
resource.usages.incrementAndGet();
return resource;
}
interface Resource {
String ID = "org.junit.jupiter.extensions.Heavyweight.Resource";
int usages();
}
/**
* Demo resource class.
*
* <p>The class implements interface {@link CloseableResource}
* and interface {@link AutoCloseable} to show and ensure that a single
* {@link ResourceValue#close()} method implementation is needed to comply
* with both interfaces.
*/
private static class ResourceValue implements Resource, CloseableResource, AutoCloseable {
private static final AtomicInteger creations = new AtomicInteger();
private final AtomicInteger usages = new AtomicInteger();
@SuppressWarnings("unused") // used via reflection
ResourceValue() {
// Open long-living resources here.
assertEquals(1, creations.incrementAndGet(), "Singleton pattern failure!");
}
@Override
public void close() {
// Close resources here.
assertEquals(9, usages.get(), "Usage counter mismatch!");
}
@Override
public int usages() {
return usages.get();
}
}
private static class CloseableOnlyOnceResource implements CloseableResource {
private final AtomicBoolean closed = new AtomicBoolean();
@Override
public void close() {
assertTrue(closed.compareAndSet(false, true), "already closed!");
}
}

It is also a parameter resolver, so it can be injected into test methods:

/**
* Unit tests for {@link org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource}
* stored values.
*
* @since 1.1
*/
@ExtendWith(Heavyweight.class)
@ResourceLock(Heavyweight.Resource.ID)
class HeavyweightAlphaTests {
private static int mark;
@BeforeAll
static void setMark(Heavyweight.Resource resource) {
assertTrue(resource.usages() > 0);
mark = resource.usages();
}
@TestFactory
Stream<DynamicTest> alpha1(Heavyweight.Resource resource) {
return Stream.of(dynamicTest("foo", () -> assertTrue(resource.usages() > 1)));
}
@Test
void alpha2(Heavyweight.Resource resource) {
assertTrue(resource.usages() > 1);
}
@Test
void alpha3(Heavyweight.Resource resource) {
assertTrue(resource.usages() > 1);
}
@AfterAll
static void checkMark(Heavyweight.Resource resource) {
assertEquals(mark, resource.usages() - 4);
}
}

Mind the @ResourceLock(Heavyweight.Resource.ID) annotation. Global resources need synchronization guarding when Jupiter executes tests in parallel: https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-parallel-execution

@sbrannen
Copy link
Member Author

Thanks for providing the examples, @sormuras.

With regard to the last point, @ResourceLock is unnecessary if the CloseableResource is itself thread-safe and designed for concurrent access -- right?

@sormuras
Copy link
Member

sormuras commented Nov 9, 2018

With regard to the last point, @ResourceLock is unnecessary if the CloseableResource is itself thread-safe and designed for concurrent access -- right?

Absolutely! I'm preparing a new default extension which will provide a @Singleton annotation to support exactly those use cases.

sormuras added a commit that referenced this issue Nov 9, 2018
sormuras added a commit that referenced this issue Nov 10, 2018
sormuras added a commit that referenced this issue Nov 10, 2018
sormuras added a commit that referenced this issue Nov 10, 2018
sormuras added a commit that referenced this issue Jan 19, 2019
sormuras added a commit that referenced this issue Jan 19, 2019
sormuras added a commit that referenced this issue Jan 27, 2019
sormuras added a commit that referenced this issue Jan 27, 2019
This commit also provides Temporary, an implementation that creates
and deletes temporary directories.

Addresses #1555
sormuras added a commit that referenced this issue Jan 27, 2019
This commit also provides Temporary, an implementation that creates
and deletes temporary directories. The Temporary.Directory annotation
can be used as an abbreviation for `@New(Temporary.class)`.

Addresses #1555
@marcphilipp marcphilipp modified the milestones: 5.5 Backlog, 5.5 M2 Apr 6, 2019
@marcphilipp marcphilipp modified the milestones: 5.5 M2, 5.5 Backlog Apr 18, 2019
@Tofel
Copy link

Tofel commented Apr 25, 2019

That code snippet is invalid. As it is, that code is executed after every test class. The correct way is to use root context:

context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL).put("my_report", new CloseableOnlyOnceResource());

Anyway, thanks for it. Solved my issue after a bit of digging!

@vyarosh
Copy link

vyarosh commented Apr 26, 2019

@Tofel I find the statement "invalid" inappropriate to working code. I agree that it's kind of disadvantage that it's executed for each test class, though I did "lazy initialization" ex:

if (my_report == null) {
    //perfom init here
}
return my_report

and I don't care about executing per class anymore

@Tofel
Copy link

Tofel commented Apr 26, 2019

@vyarosh don't get me wrong, your snipped helped me to solve the issue, so props for that. But as it is it does not work as expected (executed only 1 after all tests) in case, when you have more than 1 test class. And if you don't have more than 1 test class afterAll would the the trick way better.

As I said, thanks for the snippet! I really hope that in jUnit 5.5 we will see a real deal before/afterSuite.

@vyarosh
Copy link

vyarosh commented Apr 26, 2019

@Tofel no worries, thank you for reply. I really missed the point that it's executed for class. And yeah surfed really a lot before found working solution and waiting for before/afterSuite(fingers-crossed) as well.

@gokareless
Copy link

gokareless commented Apr 6, 2020

@sbrannen this one isn't up-for-grabs any more and is resolved in Junit 5.5 (reference javadoc) and via example. Is that right?

@jbduncan
Copy link
Contributor

It sounds like this issue should be kept open, reading the OP.

@stale
Copy link

stale bot commented Jun 19, 2022

This issue has been automatically marked as stale because it has not had recent activity. Given the limited bandwidth of the team, it will be automatically closed if no further activity occurs. Thank you for your contribution.

@stale stale bot added the status: stale label Jun 19, 2022
@sbrannen
Copy link
Member Author

Let's keep this one open since it's a recurring topic.

@SveinKare
Copy link
Contributor

SveinKare commented Jan 20, 2024

This issue is still up-for-grabs, because the example you're referring to is only used for internal tests. To resolve this issue, the user guide should show or list such an example, but probably a more realistic one. Maybe a com.sun.net.httpserver.HttpServer?

@marcphilipp Is this the type of example you had in mind, or should it be a bit more detailed? This is my first contribution, so I appreciate any feedback.

import com.sun.net.httpserver.HttpServer;
import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

/**
 * Demonstrates an implementation of {@link CloseableResource} using an {@link HttpServer}.
 */
public class HttpServerResource implements CloseableResource {
  private final HttpServer httpServer;

  /**
   * Initializes the Http server resource, using the given port.
   *
   * @param port (int) The port number for the server, must be in the range 0-65535.
   * @throws IOException if an IOException occurs during initialization.
   */
  public HttpServerResource(int port) throws IOException {
    this.httpServer = HttpServer.create(new InetSocketAddress(port), 0);
  }

  /**
   * Starts the Http server with an example handler.
   */
  public void start() {
    //Example handler
    httpServer.createContext("/example", exchange -> {
      String test = "This is a test.";
      exchange.sendResponseHeaders(200, test.length());
      try (OutputStream os = exchange.getResponseBody()) {
        os.write(test.getBytes());
      }
    });
    httpServer.setExecutor(null);
    httpServer.start();
  }

  @Override
  public void close() throws Throwable {
    httpServer.stop(0);
  }
}

@marcphilipp
Copy link
Member

@SveinKare Yes, plus a discussion in which ExtensionContext such a resource should be stored. For example, creating it lazily using Store.getOrComputeIfAbsent and storing it in the root context, would ensure it's created at most once per execution.

SveinKare added a commit to SveinKare/junit5 that referenced this issue Jun 1, 2024
SveinKare added a commit to SveinKare/junit5 that referenced this issue Jun 1, 2024
SveinKare added a commit to SveinKare/junit5 that referenced this issue Jun 1, 2024
SveinKare added a commit to SveinKare/junit5 that referenced this issue Jun 1, 2024
@sbrannen sbrannen changed the title Document how to use CloseableResource Document how to use CloseableResource Jun 3, 2024
@sbrannen sbrannen added this to the 5.11 M3 milestone Jun 3, 2024
@sbrannen
Copy link
Member Author

sbrannen commented Jun 3, 2024

Assigned to @SveinKare and 5.11 M3 in light of PR #3840.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment