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

Introduce parseable DiscoverySelector representations #3737

Merged
merged 43 commits into from May 17, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
64be199
Initial PoC of DiscoverySelectors.parse
leonard84 Dec 15, 2022
21e4886
Consistently use DiscoverySelectors methods to reuse validation logic
leonard84 Dec 16, 2022
44bca4c
Add a DiscoverySelector.toSelectorString() ..
leonard84 Dec 16, 2022
9eebabd
Use URLEncode for all characters in UniqueId segment values
leonard84 Dec 16, 2022
d824ba7
Add SelectorParserContext
leonard84 Sep 13, 2023
3c00a82
Fix deprecations and code style issues
leonard84 Mar 15, 2024
5b6d01b
spotlessApply
leonard84 Mar 15, 2024
a51c09b
Introduce TBD as replacement of URI
leonard84 Mar 15, 2024
0bd2e21
Revert "Use URLEncode for all characters in UniqueId segment values"
leonard84 Mar 15, 2024
360bacc
Remove UriEncoding again
leonard84 Mar 15, 2024
d64e235
Rename TBD to DiscoverySelectorIdentifier
marcphilipp Mar 21, 2024
9312afe
Allow covariant return types to avoid casting
marcphilipp Mar 21, 2024
a5545e4
Merge branch 'main'
marcphilipp Mar 21, 2024
9d91596
Make selectors return identifiers
marcphilipp Mar 21, 2024
a114a29
Add more tests and fix implementations
marcphilipp Mar 21, 2024
e0a3f45
Introduce --select and revamp --select-iteration to use identifiers
marcphilipp Mar 21, 2024
0ffc4c9
Reuse fully-qualified method name utilities for method selectors
marcphilipp Mar 22, 2024
9e8ffd8
Introduce StringUtils.splitIntoTwo to reduce duplication
marcphilipp Mar 22, 2024
ee4707f
Change parser to return Optional rather than Stream
marcphilipp Mar 22, 2024
6f542f0
Reuse selectMethod(String)
marcphilipp Mar 22, 2024
f391f83
Add Javadoc
marcphilipp Mar 22, 2024
a80e505
Fix module descriptor
marcphilipp Mar 22, 2024
4fdca80
Revert accidental side effect of rename
marcphilipp Mar 22, 2024
fe9cd57
Add TODO
marcphilipp Mar 22, 2024
720583b
Polish formatting
marcphilipp Mar 22, 2024
252f02f
Mark parsers internal
marcphilipp Mar 22, 2024
b0343b7
Mark toIdentifier() experimental
marcphilipp Mar 22, 2024
8d4644a
Polishing
marcphilipp Mar 22, 2024
bc80531
Polishing
marcphilipp May 1, 2024
9aeec78
Add tests for CollectionUtils#getFirstElement
marcphilipp May 1, 2024
5b3f23a
Merge remote-tracking branch 'origin/main' into leo/discovery-selecto…
marcphilipp May 1, 2024
9ce4e20
Add to release notes
marcphilipp May 1, 2024
9967df6
Load parsers at most once per class loader
marcphilipp May 15, 2024
0c14cc6
Add explicit anchors
marcphilipp May 15, 2024
6143133
Document discovery selectors in User Guide
marcphilipp May 15, 2024
4dc3a7c
Add todo
marcphilipp May 15, 2024
27d1542
Reduce table width
marcphilipp May 15, 2024
964c27b
Add links to Javadoc
marcphilipp May 15, 2024
a31381c
Delete empty paragraph
marcphilipp May 15, 2024
81b6694
Add type-level Javadoc
marcphilipp May 15, 2024
0688942
Introduce `Select` annotation for test suites
marcphilipp May 16, 2024
2bd52e1
Collapse index ranges when formatting iteration selector identifiers
marcphilipp May 16, 2024
a7f4868
Link to suite annotation Javadoc
marcphilipp May 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -12,6 +12,8 @@

import static org.apiguardian.api.API.Status.STABLE;

import java.util.Optional;

import org.apiguardian.api.API;

/**
Expand All @@ -25,4 +27,16 @@
*/
@API(status = STABLE, since = "1.0")
public interface DiscoverySelector {

/**
* Return a {@link String} representation of this selector.
* <p>
* The returned string has to be parsable by a corresponding
* {@link org.junit.platform.engine.discovery.SelectorParser}.
* <p>
* @return a {@link String} representation of this selector or empty if it is not supported.
*/
default Optional<String> toSelectorString() {
return Optional.empty();
}
}
Expand Up @@ -14,6 +14,8 @@
import static org.apiguardian.api.API.Status.STABLE;

import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

import org.apiguardian.api.API;
import org.junit.platform.commons.PreconditionViolationException;
Expand Down Expand Up @@ -133,4 +135,26 @@ public String toString() {
// @formatter:on
}

@Override
public Optional<String> toSelectorString() {
return Optional.of(String.format("%s:%s", Parser.PREFIX, this.className));
}

public static class Parser implements SelectorParser {

private static final String PREFIX = "class";

public Parser() {
}

@Override
public String getPrefix() {
return PREFIX;
}

@Override
public Stream<DiscoverySelector> parse(TBD selector, SelectorParserContext context) {
return Stream.of(DiscoverySelectors.selectClass(selector.getValue()));
}
}
}
Expand Up @@ -12,8 +12,10 @@

import static org.apiguardian.api.API.Status.STABLE;

import java.net.URI;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

import org.apiguardian.api.API;
import org.junit.platform.commons.util.ToStringBuilder;
Expand Down Expand Up @@ -101,4 +103,42 @@ public String toString() {
this.position).toString();
}

@Override
public Optional<String> toSelectorString() {
return Optional.of(
String.format("%s:%s", Parser.PREFIX, CodingUtil.normalizeDirectorySeparators(this.classpathResourceName)));
}

public static class Parser implements SelectorParser {

private static final String PREFIX = "classpath";

public Parser() {
}

@Override
public String getPrefix() {
return PREFIX;
}

@Override
public Stream<DiscoverySelector> parse(TBD selector, SelectorParserContext context) {
String part = selector.getValue();

// Unfortunately, URI only parses the query if you have scheme://something?query
int queryIndex = part.indexOf('?');
String resourceName;
FilePosition position;
if (queryIndex == -1) {
resourceName = part;
position = null;
}
else {
resourceName = part.substring(0, queryIndex);
position = FilePosition.fromQuery(part.substring(queryIndex + 1)).orElse(null);
}

return Stream.of(DiscoverySelectors.selectClasspathResource(resourceName, position));
}
}
}
Expand Up @@ -13,7 +13,11 @@
import static org.apiguardian.api.API.Status.STABLE;

import java.net.URI;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

import org.apiguardian.api.API;
import org.junit.platform.commons.util.ToStringBuilder;
Expand Down Expand Up @@ -82,4 +86,29 @@ public String toString() {
return new ToStringBuilder(this).append("classpathRoot", this.classpathRoot).toString();
}

@Override
public Optional<String> toSelectorString() {
return Optional.of(String.format("%s:%s", Parser.PREFIX,
CodingUtil.normalizeDirectorySeparators(String.valueOf(this.classpathRoot))));
}

public static class Parser implements SelectorParser {

private static final String PREFIX = "classpath-root";

public Parser() {

}

@Override
public String getPrefix() {
return PREFIX;
}

@Override
public Stream<DiscoverySelector> parse(TBD selector, SelectorParserContext context) {
return DiscoverySelectors.selectClasspathRoots(
Collections.singleton(Paths.get(selector.getValue()))).stream().map(DiscoverySelector.class::cast);
}
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2015-2024 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.engine.discovery;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

final class CodingUtil {
private CodingUtil() {
}

static String urlDecode(String encoded) {
try {
return URLDecoder.decode(encoded, "UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 is not supported", e);
}
}

static String urlEncode(String plain) {
try {
return URLEncoder.encode(plain, "UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 is not supported", e);
}
}

static String normalizeDirectorySeparators(String path) {
return path.replace('\\', '/');
}
}
Expand Up @@ -13,11 +13,14 @@
import static org.apiguardian.api.API.Status.STABLE;

import java.io.File;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

import org.apiguardian.api.API;
import org.junit.platform.commons.util.ToStringBuilder;
Expand Down Expand Up @@ -107,4 +110,25 @@ public String toString() {
return new ToStringBuilder(this).append("path", this.path).toString();
}

@Override
public Optional<String> toSelectorString() {
return Optional.of(String.format("%s:%s", Parser.PREFIX, CodingUtil.normalizeDirectorySeparators(this.path)));
}
public static class Parser implements SelectorParser {

private static final String PREFIX = "directory";

public Parser() {
}

@Override
public String getPrefix() {
return PREFIX;
}

@Override
public Stream<DiscoverySelector> parse(TBD selector, SelectorParserContext context) {
return Stream.of(DiscoverySelectors.selectDirectory(selector.getValue()));
}
}
}
Expand Up @@ -23,6 +23,7 @@
import java.nio.file.Path;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

import org.apiguardian.api.API;
import org.junit.platform.commons.PreconditionViolationException;
Expand Down Expand Up @@ -937,4 +938,7 @@ public static IterationSelector selectIteration(DiscoverySelector parentSelector
return new IterationSelector(parentSelector, iterationIndices);
}

public static Stream<DiscoverySelector> parse(String selector) {
return new SelectorParsers().parse(selector);
}
}
Expand Up @@ -153,6 +153,14 @@ public Optional<Integer> getColumn() {
return Optional.ofNullable(this.column);
}

String toQueryPart() {
StringBuilder builder = new StringBuilder("line=").append(this.line);
if (this.column != null) {
builder.append("&column=").append(this.column);
}
return builder.toString();
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
Expand Up @@ -13,12 +13,14 @@
import static org.apiguardian.api.API.Status.STABLE;

import java.io.File;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

import org.apiguardian.api.API;
import org.junit.platform.commons.util.ToStringBuilder;
Expand Down Expand Up @@ -117,4 +119,44 @@ public String toString() {
return new ToStringBuilder(this).append("path", this.path).append("position", this.position).toString();
}

@Override
public Optional<String> toSelectorString() {
if (this.position == null) {
return Optional.of(
String.format("%s://%s", Parser.PREFIX, CodingUtil.normalizeDirectorySeparators(this.path)));
}
else {
return Optional.of(String.format("%s://%s?%s", Parser.PREFIX,
CodingUtil.normalizeDirectorySeparators(this.path), this.position.toQueryPart()));
}
}

public static class Parser implements SelectorParser {

private static final String PREFIX = "file";

public Parser() {
}

@Override
public String getPrefix() {
return PREFIX;
}

@Override
public Stream<DiscoverySelector> parse(TBD selector, SelectorParserContext context) {
// Problem: the real file url, e.g. `file:///` does not support relative paths.
// if we use just the schemeSpecificPart and omit the `///` we can support `file:relative/path` and `file:/absolute/path`
// however it won't parse the Query part of the URI anymore, which is used to specify the line and column.
// and it won't be a standard file URI (https://en.wikipedia.org/wiki/File_URI_scheme) anymore.
// For now, we misuse the host part of the uri, so when it is set, we treat it as relative path and when it is not set, we treat it as absolute path.

URI uri = URI.create(selector.getPrefix() + ":" + selector.getValue());
String path = uri.getHost() == null ? uri.getPath() : uri.getHost() + uri.getPath();

return FilePosition.fromQuery(uri.getQuery()).map(filePosition -> Stream.<DiscoverySelector> of(
DiscoverySelectors.selectFile(path, filePosition))).orElseGet(
() -> Stream.of(DiscoverySelectors.selectFile(path)));
}
}
}
Expand Up @@ -14,11 +14,15 @@
import static java.util.stream.Collectors.toCollection;
import static org.apiguardian.api.API.Status.EXPERIMENTAL;

import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apiguardian.api.API;
import org.junit.platform.commons.util.ToStringBuilder;
Expand Down Expand Up @@ -90,4 +94,34 @@ public String toString() {
.toString();
// @formatter:on
}

@Override
public Optional<String> toSelectorString() {
return parentSelector.toSelectorString().map(parentSelectorString -> String.format("%s:%s#%s", //
Parser.PREFIX, //
CodingUtil.urlEncode(parentSelectorString), //
iterationIndices.stream().map(String::valueOf).collect(Collectors.joining(","))));
}

public static class Parser implements SelectorParser {

private static final String PREFIX = "iteration";

public Parser() {
}

@Override
public String getPrefix() {
return PREFIX;
}

@Override
public Stream<DiscoverySelector> parse(TBD selector, SelectorParserContext context) {
int[] iterationIndices = Arrays.stream(selector.getFragment().split(",")).mapToInt(
Integer::parseInt).toArray();
String parentSelector = CodingUtil.urlDecode(selector.getValue());
return context.parse(parentSelector).map(
parent -> DiscoverySelectors.selectIteration(parent, iterationIndices));
}
}
}