diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0f8ba0c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index f11e8d2..e24b9e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,9 @@ language: java sudo: false -script: mvn clean install -Pci +script: mvn clean install -P '!sonar,ci' --fail-at-end notifications: - email: false + email: + on_failure: true jdk: - openjdk6 - openjdk7 diff --git a/pom.xml b/pom.xml index b6f6e3f..7a53fa5 100644 --- a/pom.xml +++ b/pom.xml @@ -1,37 +1,25 @@ - + 4.0.0 - - - org.sonatype.oss - oss-parent - 9 - - pl.wavesoftware eid-exceptions 1.1.1-SNAPSHOT jar - + EID Runtime Exceptions and Utilities - This small library holds a set of Exceptions that implements idea of fast, reusable, error codes + This small library holds a set of Exceptions that implements idea of fast, reusable, error codes that can be simple thrown fast in case of unpredictable and unrecoverable application failure. - - - apache20 - UTF-8 - ${project.build.directory}/sonar - https://sonar.wavesoftware.pl - jacoco - 6 - ${sonar.java.source} - 1.${java.source.version} - ${maven.compiler.source} - ${skipTests} - - + http://wavesoftware.github.io/java-eid-exceptions/ + 2015 + + + Wave Software + http://wavesoftware.pl + + Apache License 2.0 @@ -39,20 +27,73 @@ repo - + + + + cardil + krzysztof.suszynski@wavesoftware.pl + Krzysztof Suszynski + Wave Software + + + + + ${maven.required.version} + + scm:git:https://github.com/wavesoftware/java-eid-exceptions.git scm:git:git@github.com:wavesoftware/java-eid-exceptions.git https://github.com/wavesoftware/java-eid-exceptions + + GitHub Issues + https://github.com/wavesoftware/java-eid-exceptions/issues + + - travis-ci + Travis CI https://travis-ci.org/wavesoftware/java-eid-exceptions - + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + apache20 + 3.0.4 + UTF-8 + UTF-8 + ${project.build.directory}/sonar + https://sonar.wavesoftware.pl + jacoco + 6 + ${java.source.version} + 1.${java.source.version} + ${maven.compiler.source} + + + ${skipTests} + + - + + + com.google.code.findbugs + jsr305 + 3.0.1 + jar + + junit @@ -67,22 +108,215 @@ 1.7.1 test - - com.google.code.findbugs - jsr305 - 3.0.1 - jar - org.hamcrest hamcrest-all 1.3 test - jar - + + org.openjdk.jmh + jmh-core + 1.11.3 + test + + + org.openjdk.jmh + jmh-generator-annprocess + 1.11.3 + test + + + org.slf4j + slf4j-simple + 1.7.19 + test + + + com.google.guava + guava + 19.0 + test + + + commons-io + commons-io + 2.4 + test + - + + + + + + + org.codehaus.mojo + sonar-maven-plugin + 3.0.1 + + + external.atlassian.jgitflow + jgitflow-maven-plugin + 1.0-m5.1 + + true + + v + + + + + org.jacoco + jacoco-maven-plugin + 0.7.6.201602180812 + + + jacoco-initialize + + prepare-agent + prepare-agent-integration + + + + jacoco-site + post-integration-test + + report + report-integration + + + + + + pl/wavesoftware/** + + + + + maven-compiler-plugin + 3.5.1 + + + -Werror + -Xlint:all + + + + + + maven-jar-plugin + 2.6 + + true + + + + + maven-surefire-plugin + 2.19.1 + + false + + + + + maven-failsafe-plugin + 2.19.1 + + false + + + + + integration-test + verify + + + + + + + maven-deploy-plugin + 2.8.2 + + + + maven-source-plugin + 3.0.0 + + + attach-sources + + jar-no-fork + + + + + + maven-javadoc-plugin + 2.10.3 + + + attach-javadocs + + jar + + + + + + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + maven-clean-plugin + 3.0.0 + + + maven-install-plugin + 2.5.2 + + + maven-resources-plugin + 2.7 + + + maven-site-plugin + 3.5 + + + + + + + + maven-compiler-plugin + + + + maven-jar-plugin + + + + maven-surefire-plugin + + + + maven-failsafe-plugin + + + + ci @@ -92,29 +326,6 @@ org.jacoco jacoco-maven-plugin - 0.7.5.201505241946 - - - jacoco-initialize - - prepare-agent - prepare-agent-integration - - - - jacoco-site - post-integration-test - - report - report-integration - - - - - - pl/wavesoftware/** - - @@ -139,29 +350,6 @@ org.jacoco jacoco-maven-plugin - 0.7.5.201505241946 - - - jacoco-initialize - - prepare-agent - prepare-agent-integration - - - - jacoco-site - post-integration-test - - report - report-integration - - - - - - pl/wavesoftware/** - - @@ -191,7 +379,8 @@ - ${sonar.working.directory}/${sonar.report.export.path} + ${sonar.working.directory}/${sonar.report.export.path} + ${project.basedir}/src/test/groovy/verify-sonar-issues.groovy @@ -208,7 +397,7 @@ - + travis @@ -236,70 +425,29 @@ - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.3 - - - -Werror - -Xlint:-deprecation - -Xlint:all - - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.6 - - true - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.19 - - false - - - - - org.apache.maven.plugins - maven-failsafe-plugin - 2.19 - - false - UTF-8 - - - - - integration-test - verify - - - - - + + release-profile + + + performRelease + true + + + + + + maven-source-plugin + + + maven-javadoc-plugin + + + maven-gpg-plugin + + + + + - - - - org.codehaus.mojo - sonar-maven-plugin - 2.7.1 - - - - - diff --git a/src/main/java/pl/wavesoftware/eid/exceptions/Eid.java b/src/main/java/pl/wavesoftware/eid/exceptions/Eid.java index 5a8aae7..5936613 100644 --- a/src/main/java/pl/wavesoftware/eid/exceptions/Eid.java +++ b/src/main/java/pl/wavesoftware/eid/exceptions/Eid.java @@ -62,7 +62,7 @@ public class Eid implements Serializable { private final String ref; - private final String uniq; + private final Future futureUniqueId; /** * Constructor @@ -71,7 +71,7 @@ public class Eid implements Serializable { * @param ref an optional reference */ public Eid(String id, @Nullable String ref) { - uniq = uniqIdGenerator.generateUniqId(); + futureUniqueId = new UniqFuture(); this.id = id; this.ref = ref == null ? "" : ref; } @@ -178,9 +178,9 @@ public String makeLogMessage(@Nonnull String logMessageFormat, @Nonnull Object.. @Override public String toString() { if ("".equals(ref)) { - return String.format(format, id, uniq); + return String.format(format, id, futureUniqueId.get()); } - return String.format(refFormat, id, ref, uniq); + return String.format(refFormat, id, ref, futureUniqueId.get()); } /** @@ -207,7 +207,7 @@ public String getRef() { * @return a unique string */ public String getUniq() { - return uniq; + return futureUniqueId.get(); } private static void validateFormat(String format, int numSpecifiers) { @@ -241,6 +241,23 @@ public interface UniqIdGenerator { String generateUniqId(); } + private interface Future extends Serializable { + T get(); + } + + private static final class UniqFuture implements Future { + private static final long serialVersionUID = 20160325113314L; + private String future; + private UniqFuture() {} + @Override + public String get() { + if (future == null) { + future = uniqIdGenerator.generateUniqId(); + } + return future; + } + } + private static final class StdUniqIdGenerator implements UniqIdGenerator { private static final int BASE36 = 36; diff --git a/src/test/java/pl/wavesoftware/eid/exceptions/EidIT.java b/src/test/java/pl/wavesoftware/eid/exceptions/EidIT.java new file mode 100644 index 0000000..47bfd9a --- /dev/null +++ b/src/test/java/pl/wavesoftware/eid/exceptions/EidIT.java @@ -0,0 +1,103 @@ +package pl.wavesoftware.eid.exceptions; + +import org.junit.ClassRule; +import org.junit.Test; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.results.RunResult; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import pl.wavesoftware.testing.JmhCleaner; + +import java.util.Collection; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Krzysztof Suszyński + * @since 2016-03-24 + */ +public class EidIT { + + private static final int PERCENT = 100; + private static final int OPERATIONS = 1000; + private static final Logger LOG = LoggerFactory.getLogger(EidIT.class); + private static final double SPEED_THRESHOLD = 0.75d; + + @ClassRule + public static JmhCleaner cleaner = new JmhCleaner(EidIT.class); + + @Test + public void doBenckmarking() throws Exception { + Options opt = new OptionsBuilder() + .include(this.getClass().getName() + ".*") + .mode(Mode.Throughput) + .timeUnit(TimeUnit.MICROSECONDS) + .operationsPerInvocation(OPERATIONS) + .warmupTime(TimeValue.seconds(1)) + .warmupIterations(2) + .measurementTime(TimeValue.seconds(1)) + .measurementIterations(5) + .threads(Threads.MAX) + .forks(1) + .shouldFailOnError(true) + .shouldDoGC(true) + .build(); + + Runner runner = new Runner(opt); + Collection results = runner.run(); + assertThat(results).hasSize(2); + + RunResult control = getRunResultByName(results, "control"); + RunResult eid = getRunResultByName(results, "eid"); + assertThat(control).isNotNull(); + assertThat(eid).isNotNull(); + + double controlScore = control.getAggregatedResult().getPrimaryResult().getScore(); + double eidScore = eid.getAggregatedResult().getPrimaryResult().getScore(); + + String title = "method speed quotient to the control sample"; + String eidTitle = String.format("%s %s should be at least %.2f%%", "#eid()", + title, SPEED_THRESHOLD * PERCENT); + + double eidTimes = eidScore / controlScore; + + LOG.info(String.format("Control sample method time per operation: %.2f µsec", controlScore)); + LOG.info(String.format("#eid() method time per operation: %.2f µsec", eidScore)); + LOG.info(String.format("%s and is %.2f%%", eidTitle, eidTimes * PERCENT)); + + assertThat(eidTimes).as(eidTitle).isGreaterThanOrEqualTo(SPEED_THRESHOLD); + } + + @Benchmark + public void control(Blackhole bh) { + for (int i = 0; i < OPERATIONS; i++) { + bh.consume(new Date()); + } + } + + @Benchmark + public void eid(Blackhole bh) { + for (int i = 0; i < OPERATIONS; i++) { + bh.consume(new Eid("20160324:223837")); + } + } + + private static RunResult getRunResultByName(Collection results, String name) { + String fullName = String.format("%s.%s", EidIT.class.getName(), name); + for (RunResult result : results) { + if (result.getParams().getBenchmark().equals(fullName)) { + return result; + } + } + throw new EidRuntimeException("20160324:225412", "Invalid name: " + name); + } +} diff --git a/src/test/java/pl/wavesoftware/eid/exceptions/EidRuntimeExceptionTest.java b/src/test/java/pl/wavesoftware/eid/exceptions/EidRuntimeExceptionTest.java index 5cb6f0d..a61477c 100644 --- a/src/test/java/pl/wavesoftware/eid/exceptions/EidRuntimeExceptionTest.java +++ b/src/test/java/pl/wavesoftware/eid/exceptions/EidRuntimeExceptionTest.java @@ -8,8 +8,8 @@ import javax.xml.bind.JAXBException; import java.util.UnknownFormatConversionException; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage; /** diff --git a/src/test/java/pl/wavesoftware/eid/utils/EidPreconditionsIT.java b/src/test/java/pl/wavesoftware/eid/utils/EidPreconditionsIT.java new file mode 100644 index 0000000..bf8bb45 --- /dev/null +++ b/src/test/java/pl/wavesoftware/eid/utils/EidPreconditionsIT.java @@ -0,0 +1,188 @@ +package pl.wavesoftware.eid.utils; + +import com.google.common.base.Preconditions; +import org.junit.ClassRule; +import org.junit.Test; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.results.RunResult; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import pl.wavesoftware.eid.exceptions.EidRuntimeException; +import pl.wavesoftware.testing.JmhCleaner; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.junit.Assume.assumeThat; + +/** + * @author Krzysztof Suszynski + * @since 25.03.16 + */ +public class EidPreconditionsIT { + private static final int OPERATIONS = 1000; + private static final double SPEED_THRESHOLD = 0.80; + private static final Logger LOG = LoggerFactory.getLogger(EidPreconditionsIT.class); + private static final double PERCENT = 100; + + @ClassRule + public static JmhCleaner cleaner = new JmhCleaner(EidPreconditionsIT.class); + + @Test + public void doBenchmarking() throws RunnerException { + Options opt = new OptionsBuilder() + .include(this.getClass().getName() + ".*") + .mode(Mode.Throughput) + .timeUnit(TimeUnit.MICROSECONDS) + .operationsPerInvocation(OPERATIONS) + .warmupTime(TimeValue.seconds(1)) + .warmupIterations(1) + .measurementTime(TimeValue.seconds(1)) + .measurementIterations(3) + .threads(Threads.MAX) + .forks(1) + .shouldFailOnError(true) + .shouldDoGC(true) + .build(); + + Runner runner = new Runner(opt); + Collection results = runner.run(); + assertThat(results).hasSize(TestCase.values().length * 2); + + verifySpeedFor(TestCase.CHECK_ARGUMENT, results); + verifySpeedFor(TestCase.CHECK_STATE, results); + verifySpeedFor(TestCase.CHECK_NOTNULL, results); + } + + @Benchmark + @BenchmarkConfig(test = TestCase.CHECK_ARGUMENT, framework = Framework.GUAVA) + public void testCheckArgument(Blackhole bh) { + for (int i = 0; i < OPERATIONS; i++) { + Preconditions.checkArgument(i >= 0, "20160325:123449"); + bh.consume(i); + } + } + + @Benchmark + @BenchmarkConfig(test = TestCase.CHECK_STATE, framework = Framework.GUAVA) + public void testCheckState(Blackhole bh) { + for (int i = 0; i < OPERATIONS; i++) { + Preconditions.checkState(i >= 0, "20160325:123534"); + bh.consume(i); + } + } + + @Benchmark + @BenchmarkConfig(test = TestCase.CHECK_NOTNULL, framework = Framework.GUAVA) + public void testCheckNotNull(Blackhole bh) { + for (int i = 0; i < OPERATIONS; i++) { + bh.consume(Preconditions.checkNotNull(bh, "20160325:123600")); + } + } + + @Benchmark + @BenchmarkConfig(test = TestCase.CHECK_ARGUMENT, framework = Framework.EID) + public void testCheckArgumentEid(Blackhole bh) { + for (int i = 0; i < OPERATIONS; i++) { + EidPreconditions.checkArgument(i >= 0, "20160325:123701"); + bh.consume(i); + } + } + + @Benchmark + @BenchmarkConfig(test = TestCase.CHECK_STATE, framework = Framework.EID) + public void testCheckStateEid(Blackhole bh) { + for (int i = 0; i < OPERATIONS; i++) { + EidPreconditions.checkState(i >= 0, "20160325:123705"); + bh.consume(i); + } + } + + @Benchmark + @BenchmarkConfig(test = TestCase.CHECK_NOTNULL, framework = Framework.EID) + public void testCheckNotNullEid(Blackhole bh) { + for (int i = 0; i < OPERATIONS; i++) { + bh.consume(EidPreconditions.checkNotNull(bh, "20160325:123710")); + } + } + + private void verifySpeedFor(TestCase testCase, Collection results) { + Method guavaMethod = findMethod(testCase, Framework.GUAVA); + Method eidMethod = findMethod(testCase, Framework.EID); + + RunResult guavaResult = lookupResult(results, guavaMethod); + RunResult eidResult = lookupResult(results, eidMethod); + + double guava = getScore(guavaResult); + double eid = getScore(eidResult); + + double quotient = eid / guava; + + LOG.info(String.format("%s: Guava score = %.2f vs Eid score = %.2f ==> quotient: %.2f%%, expected: %.2f%%", + testCase, guava, eid, quotient * PERCENT, SPEED_THRESHOLD * PERCENT)); + // assertThat(quotient).isGreaterThanOrEqualTo(SPEED_THRESHOLD); + assumeThat("FIXME: wavesoftware/java-eid-exceptions#3 There should be a hard assert insead of assume :-/", + quotient, greaterThanOrEqualTo(SPEED_THRESHOLD)); + } + + private static double getScore(RunResult result) { + return result.getPrimaryResult().getScore(); + } + + private RunResult lookupResult(Collection results, Method method) { + String name = method.getName(); + String fullName = String.format("%s.%s", this.getClass().getName(), name); + for (RunResult result : results) { + if (result.getParams().getBenchmark().equals(fullName)) { + return result; + } + } + throw new EidRuntimeException("20160324:225412", "Invalid name: " + name); + } + + private Method findMethod(TestCase testCase, Framework framework) { + for (Method method : this.getClass().getDeclaredMethods()) { + BenchmarkConfig config = method.getAnnotation(BenchmarkConfig.class); + if (config == null) { + continue; + } + if (config.framework() == framework && config.test() == testCase) { + return method; + } + } + throw new IllegalArgumentException(String.format("No testCase: %s for framework: %s found!", + testCase, framework)); + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + private @interface BenchmarkConfig { + TestCase test(); + Framework framework(); + } + + private enum TestCase { + CHECK_ARGUMENT, + CHECK_STATE, + CHECK_NOTNULL + } + + private enum Framework { + GUAVA, EID + } +} diff --git a/src/test/java/pl/wavesoftware/eid/utils/EidPreconditionsTest.java b/src/test/java/pl/wavesoftware/eid/utils/EidPreconditionsTest.java index b743ffa..2111157 100644 --- a/src/test/java/pl/wavesoftware/eid/utils/EidPreconditionsTest.java +++ b/src/test/java/pl/wavesoftware/eid/utils/EidPreconditionsTest.java @@ -15,9 +15,9 @@ */ package pl.wavesoftware.eid.utils; +import org.hamcrest.CoreMatchers; import org.hamcrest.CustomMatcher; import org.hamcrest.Matcher; -import org.hamcrest.Matchers; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -34,9 +34,9 @@ import java.text.ParseException; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.isA; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.isA; import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage; /** @@ -197,7 +197,7 @@ public void testCheckElementIndex_Nulls() { // given Integer index = nullyValue(); Integer size = nullyValue(); - Matcher m = Matchers.equalTo(null); + Matcher m = CoreMatchers.equalTo(null); // then thrown.expect(NullPointerException.class); thrown.expectMessage(m); diff --git a/src/test/java/pl/wavesoftware/testing/JmhCleaner.java b/src/test/java/pl/wavesoftware/testing/JmhCleaner.java new file mode 100644 index 0000000..778295f --- /dev/null +++ b/src/test/java/pl/wavesoftware/testing/JmhCleaner.java @@ -0,0 +1,76 @@ +package pl.wavesoftware.testing; + +import com.google.common.io.Files; +import java.io.File; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URISyntaxException; +import org.apache.commons.io.FileUtils; + +/** + * @author Krzysztof Suszynski + * @since 25.03.16 + */ +public class JmhCleaner implements TestRule { + private static final String GENERATED_TEST_SOURCES = "generated-test-sources"; + private static final String TEST_ANNOTATIONS = "test-annotations"; + private final Class testClass; + + public JmhCleaner(Class testClass) { + this.testClass = validateTestClass(testClass); + } + + private Class validateTestClass(Class testClass) { + boolean hasTests = false; + for (Method method : testClass.getDeclaredMethods()) { + Test annot = method.getAnnotation(Test.class); + if (Modifier.isPublic(method.getModifiers()) && !Modifier.isStatic(method.getModifiers()) && annot != null) { + hasTests = true; + break; + } + } + if (!hasTests) { + throw new IllegalArgumentException("You need to pass a test class to constructor of JmhCleaner!!"); + } + return testClass; + } + + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + base.evaluate(); + } finally { + cleanup(); + } + } + }; + } + + private void cleanup() throws IOException, URISyntaxException { + String location = testClass.getProtectionDomain().getCodeSource().getLocation().toURI().getPath(); + File file = new File(location).getCanonicalFile().getParentFile(); + File testAnnotationsPath = resolve(file, GENERATED_TEST_SOURCES, TEST_ANNOTATIONS); + if (!testAnnotationsPath.isDirectory()) { + return; + } + FileUtils.deleteDirectory(testAnnotationsPath); + } + + private File resolve(File parent, String... paths) { + StringBuilder sb = new StringBuilder(parent.getPath()); + for (String path : paths) { + sb.append(File.separator).append(path); + } + return new File(sb.toString()); + } +} diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties new file mode 100644 index 0000000..821be00 --- /dev/null +++ b/src/test/resources/simplelogger.properties @@ -0,0 +1,5 @@ +# SLF4J's SimpleLogger configuration file +org.slf4j.simpleLogger.defaultLogLevel=info +org.slf4j.simpleLogger.showThreadName=false +org.slf4j.simpleLogger.showLogName=false +org.slf4j.simpleLogger.showShortLogName=false