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

🌠Feature configurable json path #85

Merged
merged 44 commits into from
Dec 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
bf0f098
Update README.md
JanSchankin Sep 28, 2021
79bf639
Update README.md
JanSchankin Sep 28, 2021
917918e
Update README.md
JanSchankin Oct 13, 2021
0c5c4b4
Removed some typos
JanSchankin Oct 13, 2021
b02770b
Update README.md
JanSchankin Oct 13, 2021
5bc3313
New graphics and explanations
jas-ppi Nov 1, 2021
4bb479e
Update README.md
JanSchankin Nov 1, 2021
919115d
Update README.md
JanSchankin Nov 1, 2021
8d0113e
Refactoring
jas-ppi Nov 1, 2021
414f075
Update README.md
JanSchankin Nov 1, 2021
d874aa0
Update README.md
JanSchankin Nov 1, 2021
1ea4f11
Update README.md
JanSchankin Nov 1, 2021
67df439
Update README.md
JanSchankin Nov 1, 2021
5c0f4ac
Update README.md
JanSchankin Nov 1, 2021
104115d
transparency
JanSchankin Nov 1, 2021
23eb0df
transparency reverted
JanSchankin Nov 1, 2021
50e741b
text revised
JanSchankin Nov 4, 2021
73dbc5d
Refactoring
jas-ppi Nov 1, 2021
96cdff2
Refactoring
jas-ppi Nov 1, 2021
8b1c233
Refactoring
jas-ppi Nov 1, 2021
ac246e8
Refactoring
jas-ppi Nov 1, 2021
7dfade5
Update README.md
JanSchankin Nov 1, 2021
92a20ba
Update README.md
JanSchankin Nov 1, 2021
61444f2
Update README.md
JanSchankin Nov 1, 2021
b4eb9a8
Update README.md
JanSchankin Nov 1, 2021
07340eb
Update README.md
JanSchankin Nov 2, 2021
481e654
Update README.md
JanSchankin Nov 2, 2021
6b29351
Update README.md
JanSchankin Nov 2, 2021
208912e
Update README.md
JanSchankin Nov 2, 2021
a38b1e6
Update README.md
JanSchankin Nov 2, 2021
53595a1
typos
JanSchankin Nov 4, 2021
39f4373
The root for relative paths can be configured
jas-ppi Nov 12, 2021
091e955
Update gradle.yml
JanSchankin Nov 12, 2021
7203be2
Update gradle.yml
JanSchankin Nov 12, 2021
3eb7735
Update gradle.yml
JanSchankin Nov 12, 2021
c3aa715
forcing git to keepo track of the tmp folder, because otherwise the f…
JanSchankin Nov 12, 2021
3dba078
SonarCloud code smells removed
JanSchankin Nov 12, 2021
a0959b6
Merge branch 'dev' into feature_configurable_json_path
JanSchankin Nov 13, 2021
b3e296f
Additional tests
JanSchankin Nov 13, 2021
9ccecfb
Typos
JanSchankin Nov 13, 2021
3dc8724
Additional tests
JanSchankin Nov 13, 2021
f7f1d5a
Typos
JanSchankin Nov 13, 2021
2f0d1db
cleanup of ignores
jas-ppi Nov 26, 2021
b975ffa
🔨 Refactoring to solve review findings
JanSchankin Dec 3, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ jobs:
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Publish Test Report
uses: mikepenz/action-junit-report@v2
if: always() # always run even if the previous step fails
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
- name: Collect coverage data with Gradle
run: ./gradlew codeCoverageReport
- name: Analyze quality with sonar using Gradle
Expand Down
11 changes: 7 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ build

# Ignore Intellij-Settings
.idea
/deepsampler-junit4/de/
/deepsampler-junit4/out/
/deepsampler-junit5/de/
/deepsampler-junit5/out/

#ignore tmp test data
/deepsampler-junit4/src/test/tmp/de
/deepsampler-junit4/src/test/tmp/my/
/deepsampler-junit4/src/test/tmp/aSamplerCanBeSavedUsingAJsonExtension.json
/deepsampler-junit5/src/test/tmp/de
/deepsampler-junit5/src/test/tmp/my/
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ subprojects {
}

dependencies {
testImplementation("org.mockito:mockito-core:3.3.3")
testImplementation("org.mockito:mockito-core:4.0.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.0")

testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.0")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package de.ppi.deepsampler.junit;

public class AnnotationConstants {

private AnnotationConstants() {
// This class is a collection of annotations only, instantiation is not intended.
}

/**
* Default values in annotations must not be null (given by the java spec). However, we want to calculate
* default values if the user doesn't provide a value. This calculation cannot be done inside the annotation,
* therefore we need a way to communicate, that a default value was not provided. We do this using this constant
* as a replacement for null.
*/
public static final String DEFAULT_VALUE_MUST_BE_CALCULATED = "`v°°v´$de.ppi.deepsampler.junit.AnnotationConstants$$$DefaultValueMustBeCalculated$";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.ppi.deepsampler.junit;

/**
* Defines sources, from which json files can be loaded by @ {@link LoadSamples}.
*/
public enum FileSource {
CLASSPATH, FILE_SYSTEM
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
import de.ppi.deepsampler.persistence.json.JsonSourceManager;

import java.lang.reflect.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

/**
Expand All @@ -24,6 +27,8 @@
*/
public class JUnitPersistenceUtils {

public static final String DEFAULT_ROOT_PATH = "./";

private JUnitPersistenceUtils() {
// Private constructor since this utility class is not intended to be instantiated.
}
Expand Down Expand Up @@ -90,13 +95,77 @@ private static JsonSourceManager createSourceManagerWithJsonSerializerExtensions
final JsonSourceManager.Builder persistentSampleManagerBuilder = JsonSourceManager.builder();

applyJsonSerializersFromTestCaseAndTestFixture(testMethod, persistentSampleManagerBuilder);
Optional<SampleRootPath> rootPath = loadSampleRootPathFromTestOrSampleFixture(testMethod);

switch (loadSamples.source()) {
case FILE_SYSTEM:
Path file = createPathForFilesystem(rootPath, loadSamples.value(), testMethod);
return persistentSampleManagerBuilder.buildWithFile(file);
case CLASSPATH:
default:
String classPathResource = createPathForClasspath(loadSamples, testMethod);
return persistentSampleManagerBuilder.buildWithClassPathResource(classPathResource, testMethod.getDeclaringClass());
}
}

private static Optional<SampleRootPath> loadSampleRootPathFromTestOrSampleFixture(Method testMethod) {
SampleRootPath sampleRootPath = testMethod.getDeclaringClass().getAnnotation(SampleRootPath.class);

if (sampleRootPath != null) {
return Optional.of(sampleRootPath);
}

return JUnitSamplerUtils.loadSamplerFixtureFromMethodOrDeclaringClass(testMethod)
.map(Object::getClass)
.map(fixtureClass -> fixtureClass.getAnnotation(SampleRootPath.class));
}

/**
* Creates a {@link Path} based on the three path elements. If any of them is missing, a default value will be used. The
* path is concatenated like this: [rootPath][packagePath][fileName].
*
* @param sampleRootPath the annotation {@link SampleRootPath} that is used to configure the root path. If it is not
* supplied, the default ./ will be used.
* @param file A file name, that is resolved under rootPath. If it is not supplied, the package of the class,
* that declares testMethod, the test class name and the test method name is used.
* file must not be null. The String
* {@link AnnotationConstants#DEFAULT_VALUE_MUST_BE_CALCULATED} will be treated as not-supplied.
* This is because the empty default value of {@link LoadSamples#value()} cannot be null.
* @param testMethod The test method that is running the current test. It is used to provide default values.
* @return A path that is formatted to be used on the file system (in contrast to a path, that is formatted to be used
* on the classpath, see {@link JUnitPersistenceUtils#createPathForClasspath(LoadSamples, Method)}.
*/
static Path createPathForFilesystem(Optional<SampleRootPath> sampleRootPath, String file, Method testMethod) {
Path path = sampleRootPath.map(a -> Paths.get(a.value())).orElse(Paths.get(DEFAULT_ROOT_PATH));

if (file.equals(AnnotationConstants.DEFAULT_VALUE_MUST_BE_CALCULATED)) {
return path.resolve(testMethod.getDeclaringClass().getPackage().getName().replace(".", "/"))
.resolve(getDefaultJsonFileName(testMethod));
} else {
return path.resolve(file.replaceFirst("^[/\\\\]", ""));
}
}

if (!loadSamples.classPath().isEmpty()) {
return persistentSampleManagerBuilder.buildWithClassPathResource(loadSamples.classPath(), testMethod.getDeclaringClass());
} else if (!loadSamples.file().isEmpty()) {
return persistentSampleManagerBuilder.buildWithFile(loadSamples.file());
/**
* <p>
* Creates a Path that is used to load a sample file from the classpath. The path is based on two elements:
* <p>
* [packagePath][fileName]
* <p>
* If any of these two are missing, a default value will be used.
*
* @param loadSamples provides the packagePath and the fileName. If packagePath is not supplied, the package of
* testMethod is used. If fileName is not supplied, the name of testMethod and it's declaring
* class is used.
* @param testMethod the method that runs the current test. It is used to provide default values for path elements.
* @return a path that can be used to load a sample file from the classpath.
*/
static String createPathForClasspath(LoadSamples loadSamples, Method testMethod) {
if (loadSamples.value().equals(AnnotationConstants.DEFAULT_VALUE_MUST_BE_CALCULATED)) {
return "/" + testMethod.getDeclaringClass().getPackage().getName().replace(".", "/")
+ "/" + getDefaultJsonFileName(testMethod);
} else {
return persistentSampleManagerBuilder.buildWithClassPathResource(getDefaultJsonFileName(testMethod), testMethod.getDeclaringClass());
return loadSamples.value();
}
}

Expand All @@ -105,7 +174,8 @@ private static JsonSourceManager createSourceManagerWithJsonSerializerExtensions

applyJsonSerializersFromTestCaseAndTestFixture(testMethod, persistentSampleManagerBuilder);

final String fileName = saveSamples.file().isEmpty() ? getDefaultJsonFileNameWithFolder(testMethod) : saveSamples.file();
Optional<SampleRootPath> sampleRootPath = loadSampleRootPathFromTestOrSampleFixture(testMethod);
final Path fileName = createPathForFilesystem(sampleRootPath, saveSamples.value(), testMethod);

return persistentSampleManagerBuilder.buildWithFile(fileName);
}
Expand Down Expand Up @@ -148,6 +218,7 @@ private static Method getDefineSamplersMethod(final SamplerFixture samplerFixtur
/**
* Activates {@link UseJsonSerializers} from testMethod and it's declaring class. If both are annotated, both are activated.
* If testMethod declares {@link JsonSerializer}s for the same types as it's declaring class, the ones from testMethod override the ones from the class.
*
* @param testMethod
* @param persistentSampleManagerBuilder
*/
Expand All @@ -163,6 +234,7 @@ private static void applyAnnotatedJsonSerializers(final Method testMethod, final
/**
* Activates {@link UseJsonDeserializer}s from testMethod and it's declaring class. If both are annotated, both are activated.
* If testMethod declares {@link JsonDeserializer}s for the same types as it's declaring class, the ones from testMethod override the ones from the class.
*
* @param testMethod
* @param persistentSampleManagerBuilder
*/
Expand Down Expand Up @@ -281,10 +353,6 @@ private static <T> T instantiate(final Class<T> clazz) {
}


private static String getDefaultJsonFileNameWithFolder(final Method testMethod) {
return testMethod.getDeclaringClass().getName().replace(".", "/") + "_" + testMethod.getName() + ".json";
}

private static void applyAnnotatedBeanConverterExtension(final Method testMethod, final PersistentSampleManager persistentSampleManager) {
final UseBeanConverterExtension useBeanConverterExtensionOnMethod = testMethod.getAnnotation(UseBeanConverterExtension.class);
final UseBeanConverterExtension useBeanConverterExtensionOnClass = testMethod.getDeclaringClass().getAnnotation(UseBeanConverterExtension.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,42 @@
import java.lang.annotation.Target;

/**
* The {@link LoadSamples}-annotation may be used at any test method as a convenient way to load Samples from a JSON-file. The JSON-file is usually created
* using {@link SaveSamples}.
* The @{@link LoadSamples}-annotation may be used at any test method as a convenient way to load Samples from a
* JSON-file. The JSON-file is usually created using @{@link SaveSamples}.
* <p>
* It is possible to load the file either from a local file system (property {@link LoadSamples#file()}) or from the classpath (property {@link LoadSamples#classPath()})
* It is possible to load the file either from a local filesystem or from the classpath (property {@link LoadSamples#source()})
* <p>
* If no file-name is provided, DeepSampler loads the file from the classpath, expecting that the file is located in the same package as the test class
* and is named in the form {@code [simple class name]_[simple method name].json}. E.g. given a method {@code org.project.MyTest#testIt()} the expected file name is
* {@code /org/project/MyTest_testIt.json}
* The filename may be defined using the property {@link LoadSamples#value()}. The default filename is composed using the
* full qualified name of the test class and the test method.
* <p>
* This annotation must be used in combination with {@link UseSamplerFixture}.
* The root path for relative filenames is by default './'. It can be changed, using the annotation {@link SampleRootPath}.
* <p>
* The serialisation is done in two steps: First, the object may be converted to an abstract {@link de.ppi.deepsampler.persistence.model.PersistentBean}. This is done
* to omit type information in the JSON-Files. Primitive types, arrays, {@link java.util.List}s and {@link java.util.Map}s are usually not converted to a
* {@link de.ppi.deepsampler.persistence.model.PersistentBean}. They are passed to the second step unchanged. The second step is the JSON-serialisation by Jackson,
* which may include some {@link com.fasterxml.jackson.databind.JsonSerializer}s
* This annotation must be used in combination with @{@link UseSamplerFixture}.
* <p>
* It is possible to register some extensions to customize the serialisation using the annotations @{@link UseJsonSerializer}
* and {@link UseBeanConverterExtension}.
* It is possible to register some extensions to customize the deserialization using the annotations @{@link UseJsonDeserializer}
* and @{@link UseBeanConverterExtension}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoadSamples {


/**
* If this property is provided, the Sampler-file will be loaded from the local file system using the provided file name.
* The name of the json file. If this property is omitted, the name will be composed like this:
* [package of the test class]/[Test class name]_[test method].json
* <p>
* If the annotation {@link SampleRootPath} is present, file() will be interpreted relative to the
* supplied root path.
*
* @return the path to a Sampler-JSON-file.
* @return the name of the sample JSON file.
*/
String file() default "";
String value() default AnnotationConstants.DEFAULT_VALUE_MUST_BE_CALCULATED;

/**
* If this property is provided, the Sampler-file will be loaded from the classpath using the provided resource-name.
* Defines from where the file is loaded. Either the classpath or the vanilla filesystem can be used. Default is
* {@link FileSource#CLASSPATH}.
*
* @return the path to a Sampler-JSON-file. The path is resolved relative to the test class.
* @return The source of the file.
*/
String classPath() default "";
FileSource source() default FileSource.CLASSPATH;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package de.ppi.deepsampler.junit;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Relative paths to sample files are resolved against ./ by default. {@link SampleRootPath} can be used to define
* another root path for relative paths.
* <p>
* {@link SampleRootPath} is used for sample files that are saved by @{@link SaveSamples} and loaded by {@link LoadSamples}.
* {@link LoadSamples} uses the root path only, if the sample file is loaded from file system, not from classpath.
* <p>
* This annotation may be used on the test class and on the {@link SamplerFixture}. If both are annotated, the one on
* the test class overrides the one on the {@link SamplerFixture}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SampleRootPath {

/**
* Defines the root path for relative paths.
*
* @return The root path under which sample files are stored.
*/
String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,33 @@
import java.lang.annotation.Target;

/**
* The {@link SaveSamples}-annotation may be used at any test method as a convenient way to record samples from a running test. The recorded samples are saved as a JSON-file and
* are usually loaded by @{@link LoadSamples}
* The @{@link SaveSamples}-annotation may be used at any test method as a convenient way to record samples from a
* running test. The recorded samples are saved as a JSON-file and are usually loaded by @{@link LoadSamples}
* <p>
* The file is saved on the local file system (property {@link LoadSamples#file()}).
* The file's name is composed of two parts: [rootPath][fileName]. DeepSampler generates a default name using
* the test class and test method. In most cases this will suffice, however you can change the path in detail:
* <ul>
* <li>rootPath: The root path for relative packagePaths is by default './'. It can be changed, using the annotation
* * {@link SampleRootPath}.</li>
* <li>value(): The concrete name of the JSON-file. If omitted, the name of the test class, including it's package,
* and the test method is used.</li>
* </ul>
* <p>
* If no file-name is provided, DeepSampler creates a file name in the form {@code [simple class name]_[simple method name].json}. E.g. given a method
* {@code org.project.MyTest#testIt()} the expected file name is
* {@code ./org/project/MyTest_testIt.json}
* This annotation must be used in combination with @{@link UseSamplerFixture}.
* <p>
* This annotation must be used in combination with {@link UseSamplerFixture}.
* <p>
* The deserialization is done in two steps: First, Jackson is used to deserialize the JSON-File. If the file contains {@link de.ppi.deepsampler.persistence.model.PersistentBean}s
* a second deserialization-step is run. The {@link de.ppi.deepsampler.persistence.model.PersistentBean} is an abstract model of a java Bean without any type information. The
* second step reconstructs the type information from the sampler by analysing the sampled api and recreates the original object from the {@link de.ppi.deepsampler.persistence.model.PersistentBean}
* <p>
* It is possible to register customisations of the deserialization-process using the annotations @{@link UseJsonDeserializer} and @{@link UseBeanConverterExtension}.
* It is possible to register customisations of the serialization-process using the annotations
* {@link UseJsonSerializer} and @{@link UseBeanConverterExtension}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SaveSamples {


/**
* If this property is provided, the Sample-file will be saved to the local file system using the provided file name. If no file name is provided, the file name will be
* generated in the form {@code [simple class name]_[simple method name].json}. E.g. given a method
* {@code org.project.MyTest#testIt()} the expected file name is
* {@code ./org/project/MyTest_testIt.json}
* The name of the json file. Default is the package of the test class, followed by the name of the test class,
* followed by the name of the test method.
*
* @return the path to a Sampler-JSON-file.
* @return the name of the sample JSON file.
*/
String file() default "";
String value() default AnnotationConstants.DEFAULT_VALUE_MUST_BE_CALCULATED;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import java.lang.annotation.Target;

/**
* DeepSampler's persistence runs through two steps. First Objects are converted to abstract {@link de.ppi.deepsampler.persistence.model.PersistentBean}s and second
* DeepSampler's persistence runs through two steps. First, Objects are converted to abstract {@link de.ppi.deepsampler.persistence.model.PersistentBean}s and second
* these beans are passed to Jackson, the actual Json-Serializer.
* DeepSampler is able to convert most objects to {@link de.ppi.deepsampler.persistence.model.PersistentBean}s out of the box, but sometimes objects need a special
* conversion. This can be done using custom {@link BeanConverterExtension}s.
Expand Down
Loading