├── .java-version ├── src ├── test │ └── java │ │ └── com │ │ └── oneeyedmen │ │ └── okeydoke │ │ ├── formatters │ │ ├── StringFormatterTest.emptyIterable.approved │ │ ├── StringFormatterTest.null_is_printed.approved │ │ ├── TableFormatterTest.null_is_printed.approved │ │ ├── StringFormatterTest.a_string_is_itself.approved │ │ ├── StringFormatterTest.object_uses_toString.approved │ │ ├── StringFormatterTest.array_is_listed.approved │ │ ├── StringFormatterTest.iterable_is_listed.approved │ │ ├── TableFormatterTest.iterable_is_listed.approved │ │ ├── TableFormatterTest.array_is_listed_by_row.approved │ │ ├── TableFormatterTest.iterable_is_listed_by_row.approved │ │ ├── TableFormatterTest.table_is_laid_out.approved │ │ ├── TableFormatterTest.array_table_is_laid_out.approved │ │ ├── TableFormatterTest.iterable_table_is_laid_out.approved │ │ ├── TableFormatterTest.with_mapper.approved │ │ ├── TableFormatterTest.with_header.approved │ │ ├── StringFormatterTest.java │ │ └── TableFormatterTest.java │ │ ├── junit4 │ │ ├── Test Name Annotation Test.my test.approved │ │ ├── FileSystemApprovalsRuleTest.matches_when_approved_result_matches.approved │ │ ├── FileSystemApprovalsRuleTest.doesnt_match_when_approved_result_doesnt_match.approved │ │ ├── TheoryApprovalsRuleTest.string_length.approved │ │ ├── TheoryApprovalsRuleTest.legacyMethod_output.approved │ │ ├── TheoryApprovalsRuleTest.legacyMethod_output_reflectively.approved │ │ ├── TheoryApprovalsRuleTest.legacyMethod_output_reflectively_overloaded.approved │ │ ├── TheoryApprovalsRuleTest.can_pass_class_for_static_methods.approved │ │ ├── TheoryApprovalsRuleTest.legacyMethod_output_reflectively_overloaded2.approved │ │ ├── TestNameAnnotationTest.java │ │ ├── FormattedMatcherTest.java │ │ ├── FileSystemApprovalsRuleTest.java │ │ ├── TheoryApprovalsRuleReflectionTest.java │ │ ├── ApprovalsRuleTidiesUpTest.java │ │ ├── StandardTestNamerTest.java │ │ └── TheoryApprovalsRuleTest.java │ │ ├── junit5 │ │ ├── ApprovalsExtensionTest.shouldFailInvalidOutput.approved │ │ ├── ApprovalsExtensionTest.shouldPassValidOutput.approved │ │ ├── ApprovalsExtensionFieldTest.shouldFailInvalidOutput.approved.extension │ │ ├── ApprovalsExtensionFieldTest.shouldPassValidOutput.approved.extension │ │ ├── ApprovalsExtensionTest.java │ │ └── ApprovalsExtensionFieldTest.java │ │ ├── examples │ │ ├── ApprovalsRuleTest.something_that_we_want_to_be_the_same_next_time.approved │ │ ├── ApprovalsExtensionTest.something_that_we_want_to_be_the_same_next_time.approved │ │ ├── TranscriptTest.can_describe_what_we_are_doing_before_assertion.approved │ │ ├── Support.java │ │ ├── Calculator.java │ │ ├── ApprovalsRuleTest.java │ │ ├── ApprovalsExtensionTest.java │ │ ├── TranscriptTest.java │ │ ├── LockDownTest.legacyMethod_output.approved │ │ ├── LockDownTest.legacyMethod_checked_fluently.approved │ │ ├── LockDownTest.legacyMethod_checked_reflectively.approved │ │ └── LockDownTest.java │ │ ├── testutils │ │ ├── PrecannedApprovalsRule.java │ │ └── CleanDirectoryRule.java │ │ ├── sources │ │ ├── PopupReporterTest.java │ │ └── FileSystemSourceOfApprovalTest.java │ │ ├── util │ │ ├── DirectoryFinderTest.java │ │ ├── TestDirectoryTest.java │ │ └── TabulatorTest.java │ │ ├── StandardTranscriptTest.java │ │ ├── internal │ │ └── HexDumpTest.java │ │ ├── BinaryApproverTest.java │ │ ├── StreamingApprovalTest.java │ │ ├── checkers │ │ └── AsserterTest.java │ │ ├── BinaryApproverWithBinaryCheckerTest.java │ │ ├── ApproverTest.java │ │ └── ApproverFileLifecycleTest.java └── main │ └── java │ └── com │ └── oneeyedmen │ └── okeydoke │ ├── internal │ ├── Mapper.java │ ├── MappedIterable.java │ ├── WrappingIterator.java │ ├── MappingIterable.java │ ├── LyingWrappingIterator.java │ ├── DirectoryFinder.java │ ├── IO.java │ ├── Fred.java │ ├── MethodFinder.java │ ├── OperatingSystem.java │ └── HexDump.java │ ├── ApproverFactory.java │ ├── Checker.java │ ├── junit4 │ ├── TestNamer.java │ ├── TestDirectoryRule.java │ ├── Matchers.java │ ├── BinaryApprovalsRule.java │ ├── ApprovalsRule.java │ ├── StandardTestNamer.java │ ├── BaseApprovalsRule.java │ └── TheoryApprovalsRule.java │ ├── Reporter.java │ ├── RuntimeIOException.java │ ├── formatters │ ├── BinaryFormatter.java │ ├── InvocationFormatter.java │ ├── TableFormatter.java │ └── StringFormatter.java │ ├── Serializer.java │ ├── Name.java │ ├── junit5 │ ├── ResourcesApprovalsExtension.java │ ├── KotlinApprovalsExtension.java │ └── ApprovalsExtension.java │ ├── Resource.java │ ├── checkers │ ├── StringChecker.java │ ├── Asserter.java │ ├── HexChecker.java │ └── BinaryChecker.java │ ├── BinaryApprover.java │ ├── Formatter.java │ ├── SourceOfApproval.java │ ├── Transcript.java │ ├── Invocation.java │ ├── Serializers.java │ ├── sources │ ├── StreamingFileResource.java │ ├── LazyOutputStream.java │ ├── ComparingOutputStream.java │ ├── FileResource.java │ ├── StreamingFileSystemSourceOfApproval.java │ └── FileSystemSourceOfApproval.java │ ├── Checkers.java │ ├── reporters │ ├── PopupReporter.java │ └── CommandLineReporter.java │ ├── Approver.java │ ├── serializers │ ├── BinarySerializer.java │ └── StringSerializer.java │ ├── Formatters.java │ ├── Sources.java │ ├── Reporters.java │ ├── StandardTranscript.java │ ├── ApproverFactories.java │ ├── BaseApprover.java │ └── util │ ├── TestDirectory.java │ └── Tabulator.java ├── .gitignore ├── release.properties ├── licence.txt ├── create_readme.sh ├── README.md ├── okeydoke.iml └── pom.xml /.java-version: -------------------------------------------------------------------------------- 1 | 1.8 2 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/StringFormatterTest.emptyIterable.approved: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/Test Name Annotation Test.my test.approved: -------------------------------------------------------------------------------- 1 | banana -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/StringFormatterTest.null_is_printed.approved: -------------------------------------------------------------------------------- 1 | NULL -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/TableFormatterTest.null_is_printed.approved: -------------------------------------------------------------------------------- 1 | NULL -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/StringFormatterTest.a_string_is_itself.approved: -------------------------------------------------------------------------------- 1 | A String -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/StringFormatterTest.object_uses_toString.approved: -------------------------------------------------------------------------------- 1 | A StringBuilder -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit5/ApprovalsExtensionTest.shouldFailInvalidOutput.approved: -------------------------------------------------------------------------------- 1 | valid output -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit5/ApprovalsExtensionTest.shouldPassValidOutput.approved: -------------------------------------------------------------------------------- 1 | valid output -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/StringFormatterTest.array_is_listed.approved: -------------------------------------------------------------------------------- 1 | ["one","two","three"] -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/StringFormatterTest.iterable_is_listed.approved: -------------------------------------------------------------------------------- 1 | ["one","two","three"] -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/FileSystemApprovalsRuleTest.matches_when_approved_result_matches.approved: -------------------------------------------------------------------------------- 1 | banana -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit5/ApprovalsExtensionFieldTest.shouldFailInvalidOutput.approved.extension: -------------------------------------------------------------------------------- 1 | valid output -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit5/ApprovalsExtensionFieldTest.shouldPassValidOutput.approved.extension: -------------------------------------------------------------------------------- 1 | valid output -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/examples/ApprovalsRuleTest.something_that_we_want_to_be_the_same_next_time.approved: -------------------------------------------------------------------------------- 1 | banana42 -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/TableFormatterTest.iterable_is_listed.approved: -------------------------------------------------------------------------------- 1 | |one | 2 | |two | 3 | |three| 4 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/examples/ApprovalsExtensionTest.something_that_we_want_to_be_the_same_next_time.approved: -------------------------------------------------------------------------------- 1 | banana42 -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/TableFormatterTest.array_is_listed_by_row.approved: -------------------------------------------------------------------------------- 1 | |one | 2 | |two | 3 | |three| 4 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/TableFormatterTest.iterable_is_listed_by_row.approved: -------------------------------------------------------------------------------- 1 | |one | 2 | |two | 3 | |three| 4 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/FileSystemApprovalsRuleTest.doesnt_match_when_approved_result_doesnt_match.approved: -------------------------------------------------------------------------------- 1 | banana -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/TableFormatterTest.table_is_laid_out.approved: -------------------------------------------------------------------------------- 1 | |one |two |three | 2 | |four|five|siiiiiiix| 3 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/TheoryApprovalsRuleTest.string_length.approved: -------------------------------------------------------------------------------- 1 | [apple] -> 5 2 | [banana] -> 6 3 | [cucumber] -> 8 4 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/TableFormatterTest.array_table_is_laid_out.approved: -------------------------------------------------------------------------------- 1 | |one |two |three | 2 | |four|five|siiiiiiix| 3 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/TableFormatterTest.iterable_table_is_laid_out.approved: -------------------------------------------------------------------------------- 1 | |one |two |three | 2 | |four|five|siiiiiiix| 3 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/internal/Mapper.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.internal; 2 | 3 | public interface Mapper { 4 | public T map(U next); 5 | } 6 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/TheoryApprovalsRuleTest.legacyMethod_output.approved: -------------------------------------------------------------------------------- 1 | [apple, 5] -> apple5 2 | [banana, 6] -> banana6 3 | [cucumber, 8] -> cucumber8 4 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/TableFormatterTest.with_mapper.approved: -------------------------------------------------------------------------------- 1 | |String|Length| 2 | |------|------| 3 | |one |3 | 4 | |two |3 | 5 | |three |5 | 6 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/TheoryApprovalsRuleTest.legacyMethod_output_reflectively.approved: -------------------------------------------------------------------------------- 1 | [apple, 5] -> apple5 2 | [banana, 6] -> banana6 3 | [cucumber, 8] -> cucumber8 4 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/TheoryApprovalsRuleTest.legacyMethod_output_reflectively_overloaded.approved: -------------------------------------------------------------------------------- 1 | [apple] -> apple 2 | [banana] -> banana 3 | [cucumber] -> cucumber 4 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/TheoryApprovalsRuleTest.can_pass_class_for_static_methods.approved: -------------------------------------------------------------------------------- 1 | [apple, 5] -> apple5 2 | [banana, 6] -> banana6 3 | [cucumber, 8] -> cucumber8 4 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/examples/TranscriptTest.can_describe_what_we_are_doing_before_assertion.approved: -------------------------------------------------------------------------------- 1 | As a greengrocer 2 | I want to sing 3 | Given banana count 0 4 | I sing yes we have 0 banana(s) -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/TableFormatterTest.with_header.approved: -------------------------------------------------------------------------------- 1 | |Header 1|Header 2|Header 3 | 2 | |--------|--------|---------| 3 | |one |two |three | 4 | |four |five |siiiiiiix| 5 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/TheoryApprovalsRuleTest.legacyMethod_output_reflectively_overloaded2.approved: -------------------------------------------------------------------------------- 1 | [apple, apple] -> appleapple 2 | [banana, banana] -> bananabanana 3 | [cucumber, cucumber] -> cucumbercucumber 4 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/ApproverFactory.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | public interface ApproverFactory> { 4 | public A createApprover(String testName, Class testClass); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Checker.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | public interface Checker { 4 | public void assertEquals(ComparedT expectedMayBeNull, ComparedT actualMayBeNull) throws AssertionError; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/junit4/TestNamer.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import org.junit.runner.Description; 4 | 5 | public interface TestNamer { 6 | 7 | public String nameFor(Description description); 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/examples/Support.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.examples; 2 | 3 | public class Support { 4 | public static Object doSomeCalculation(int anInt, String aString) { 5 | return aString + anInt; 6 | } 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.actual 3 | target/ 4 | 5 | src/test/java/com/oneeyedmen/okeydoke/examples/ApprovalsRuleTest.see_how_my_IDE_reports_diffs.approved 6 | src/test/java/com/oneeyedmen/okeydoke/examples/ApprovalsRuleTest.see_how_my_IDE_reports_no_approval.approved -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Reporter.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | public interface Reporter { 4 | 5 | public void reportFailure(ApprovedStorageT actual, ActualStorageT approved, Throwable e); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/RuntimeIOException.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import java.io.IOException; 4 | 5 | public class RuntimeIOException extends RuntimeException { 6 | 7 | public RuntimeIOException(IOException x) { 8 | super(x); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/formatters/BinaryFormatter.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.formatters; 2 | 3 | import com.oneeyedmen.okeydoke.Formatter; 4 | 5 | public class BinaryFormatter implements Formatter { 6 | 7 | @Override 8 | public byte[] formatted(byte[] actual) { 9 | return actual; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Serializer.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | 7 | public interface Serializer { 8 | public void writeTo(T object, OutputStream os) throws IOException; 9 | public T readFrom(InputStream is) throws IOException; 10 | public T emptyThing(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Name.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface Name { 11 | String value(); 12 | } 13 | -------------------------------------------------------------------------------- /release.properties: -------------------------------------------------------------------------------- 1 | #release configuration 2 | #Sun Dec 10 12:20:51 GMT 2023 3 | scm.tagNameFormat=@{project.artifactId}-@{project.version} 4 | pushChanges=true 5 | scm.url=scm\:git\:git@github.com\:dmcg/okey-doke.git 6 | preparationGoals=clean verify 7 | projectVersionPolicyId=default 8 | remoteTagging=true 9 | scm.commentPrefix=[maven-release-plugin] 10 | exec.snapshotReleasePluginAllowed=false 11 | completedPhase=check-poms 12 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/junit5/ResourcesApprovalsExtension.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit5; 2 | 3 | 4 | /** 5 | * A JUnit 5 Extension to provide an Approver as a parameter to test functions. 6 | * 7 | * Stores approved files in src/test/resources 8 | */ 9 | public class ResourcesApprovalsExtension extends ApprovalsExtension { 10 | public ResourcesApprovalsExtension() { 11 | super("src/test/resources"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Resource.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.OutputStream; 7 | 8 | public interface Resource { 9 | public OutputStream outputStream() throws IOException; 10 | public InputStream inputStream() throws IOException; 11 | 12 | public void remove() throws IOException; 13 | public boolean exists(); 14 | public long size(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/checkers/StringChecker.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.checkers; 2 | 3 | import com.oneeyedmen.okeydoke.Checker; 4 | 5 | public class StringChecker implements Checker { 6 | 7 | @Override 8 | public void assertEquals(String expectedMayBeNull, String actualMayBeNull) throws AssertionError { 9 | Asserter.assertEquals("Actual was not the same as approved", expectedMayBeNull, actualMayBeNull); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/BinaryApprover.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | /** 4 | * A BinaryApprover is used to write raw bytes to file. 5 | */ 6 | public class BinaryApprover extends BaseApprover { 7 | 8 | public BinaryApprover(String testname, SourceOfApproval sourceOfApproval) { 9 | super(testname, sourceOfApproval, Formatters.binaryFormatter(), Serializers.binarySerializer(), Checkers.hexChecker()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Formatter.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | /** 4 | * Formats things of type T to things of type C (for comparison). 5 | * 6 | * Note that as the format conversion is only applied one way, it does not have to 7 | * be reversible - so don't worry too much about escaping etc. 8 | * 9 | * @param the type of the comparison. 10 | */ 11 | public interface Formatter { 12 | 13 | public C formatted(T object); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/junit5/KotlinApprovalsExtension.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit5; 2 | 3 | 4 | /** 5 | * A JUnit 5 Extension to provide an Approver as a parameter to test functions. 6 | * 7 | * Stores approved files in src/test/kotlin 8 | * 9 | * Not really needed anymore. 10 | */ 11 | public class KotlinApprovalsExtension extends ApprovalsExtension { 12 | public KotlinApprovalsExtension() { 13 | super("src/test/kotlin"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/TestNameAnnotationTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import com.oneeyedmen.okeydoke.Name; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | 7 | @Name("Test Name Annotation Test") 8 | public class TestNameAnnotationTest { 9 | 10 | @Rule public final ApprovalsRule approver = ApprovalsRule.usualRule(); 11 | 12 | @Test 13 | @Name("my test") 14 | public void test(){ 15 | approver.assertApproved("banana"); 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/SourceOfApproval.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import java.io.IOException; 4 | 5 | public interface SourceOfApproval { 6 | 7 | public Resource actualFor(String testName); 8 | 9 | public Resource approvedFor(String testName); 10 | 11 | public void checkActualAgainstApproved(String testName, Serializer serializer, Checker checker) throws AssertionError, IOException; 12 | 13 | public void reportFailure(String testName, AssertionError e); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/examples/Calculator.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.examples; 2 | 3 | import java.util.LinkedList; 4 | 5 | public class Calculator { 6 | private final LinkedList stack = new LinkedList(); 7 | private int display; 8 | 9 | public void enter(int n) { 10 | stack.push(n); 11 | } 12 | 13 | public void add() { 14 | display = stack.pop() + stack.pop(); 15 | } 16 | 17 | public String display() { 18 | return String.valueOf(display); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/checkers/Asserter.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.checkers; 2 | 3 | import org.opentest4j.AssertionFailedError; 4 | 5 | class Asserter { 6 | 7 | public static void assertEquals(String message, Object expectedMayBeNull, Object actualMayBeNull) { 8 | if ((expectedMayBeNull != null && !expectedMayBeNull.equals(actualMayBeNull)) || 9 | (expectedMayBeNull == null && actualMayBeNull != null)) 10 | throw new AssertionFailedError(message, expectedMayBeNull, actualMayBeNull); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/FormattedMatcherTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import org.junit.Test; 4 | 5 | import static com.oneeyedmen.okeydoke.Formatters.stringFormatter; 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | 8 | public class FormattedMatcherTest { 9 | 10 | @Test public void test() { 11 | String[] object = {"one, two", "three"}; 12 | String expected = stringFormatter().formatted(object); 13 | assertThat(object, Matchers.isFormatted(expected, stringFormatter())); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/junit4/TestDirectoryRule.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import com.oneeyedmen.okeydoke.util.TestDirectory; 4 | import org.junit.rules.TestWatcher; 5 | import org.junit.runner.Description; 6 | 7 | public class TestDirectoryRule extends TestWatcher { 8 | 9 | private TestDirectory testDirectory; 10 | 11 | @Override 12 | protected void starting(Description description) { 13 | testDirectory = new TestDirectory(description.getDisplayName()); 14 | } 15 | 16 | public TestDirectory dir() { 17 | return testDirectory; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/internal/MappedIterable.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.internal; 2 | 3 | public class MappedIterable extends MappingIterable { 4 | 5 | private final Mapper mapper; 6 | 7 | public static Iterable map(Iterable i, Mapper mapper) { 8 | return new MappedIterable(i, mapper); 9 | }; 10 | 11 | public MappedIterable(Iterable wrapped, Mapper mapper) { 12 | super(wrapped); 13 | this.mapper = mapper; 14 | } 15 | 16 | protected T map(U next) { 17 | return mapper.map(next); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Transcript.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | /** 4 | * The transcript provides a way to write text into the approved output. 5 | */ 6 | public interface Transcript { 7 | 8 | public Transcript appendLine(String s); 9 | 10 | public Transcript append(String s); 11 | 12 | public Transcript appendFormatted(Object o); 13 | 14 | public Transcript appendFormatted(T o, Formatter formatter); 15 | 16 | public Transcript endl(); 17 | 18 | public Transcript space(); 19 | 20 | public Transcript space(int number); 21 | 22 | boolean isStartOfLine(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/internal/WrappingIterator.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.internal; 2 | 3 | import java.util.Iterator; 4 | 5 | public abstract class WrappingIterator implements Iterator { 6 | 7 | protected final Iterator wrapped; 8 | 9 | public WrappingIterator(Iterator wrapped) { 10 | this.wrapped = wrapped; 11 | } 12 | 13 | @Override 14 | public boolean hasNext() { 15 | return wrapped.hasNext(); 16 | } 17 | 18 | @Override 19 | public abstract T next(); 20 | 21 | @Override 22 | public void remove() { 23 | wrapped.remove(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/testutils/PrecannedApprovalsRule.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.testutils; 2 | 3 | import com.oneeyedmen.okeydoke.Approver; 4 | import com.oneeyedmen.okeydoke.ApproverFactory; 5 | import com.oneeyedmen.okeydoke.junit4.ApprovalsRule; 6 | 7 | public class PrecannedApprovalsRule { 8 | 9 | public static ApprovalsRule with(final Approver delegate) { 10 | return new ApprovalsRule(new ApproverFactory() { 11 | @Override 12 | public Approver createApprover(String testName, Class testClass) { 13 | return delegate; 14 | } 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/checkers/HexChecker.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.checkers; 2 | 3 | import com.oneeyedmen.okeydoke.Checker; 4 | import com.oneeyedmen.okeydoke.Checkers; 5 | import com.oneeyedmen.okeydoke.internal.HexDump; 6 | 7 | public class HexChecker implements Checker { 8 | 9 | @Override 10 | public void assertEquals(byte[] expectedOrNull, byte[] actualOrNull) { 11 | Checkers.stringChecker().assertEquals(format(expectedOrNull), format(actualOrNull)); 12 | } 13 | 14 | private String format(byte[] bytesOrNull) { 15 | return bytesOrNull != null ? HexDump.format(bytesOrNull) : null; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Invocation.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.lang.reflect.Method; 5 | 6 | public class Invocation { 7 | 8 | public final Object[] arguments; 9 | public final Object result; 10 | 11 | public Invocation(Object o, Method method, Object[] arguments) throws InvocationTargetException, IllegalAccessException { 12 | this.arguments = arguments; 13 | result = method.invoke(o, arguments); 14 | } 15 | 16 | public Invocation(Object[] arguments, Object result) { 17 | this.arguments = arguments; 18 | this.result = result; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Serializers.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import com.oneeyedmen.okeydoke.serializers.BinarySerializer; 4 | import com.oneeyedmen.okeydoke.serializers.StringSerializer; 5 | 6 | import java.nio.charset.Charset; 7 | 8 | public class Serializers { 9 | 10 | private static final Serializer string = new StringSerializer(Charset.forName("UTF-8")); 11 | private static final Serializer binary = new BinarySerializer(); 12 | 13 | public static Serializer stringSerializer() { 14 | return string; 15 | } 16 | 17 | public static Serializer binarySerializer() { 18 | return binary; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/internal/MappingIterable.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.internal; 2 | 3 | import java.util.Iterator; 4 | 5 | public abstract class MappingIterable implements Iterable { 6 | 7 | private final Iterable wrapped; 8 | 9 | public MappingIterable(Iterable wrapped) { 10 | this.wrapped = wrapped; 11 | } 12 | 13 | @Override 14 | public Iterator iterator() { 15 | return new WrappingIterator(wrapped.iterator()) { 16 | @Override 17 | public T next() { 18 | return map(wrapped.next()); 19 | } 20 | }; 21 | } 22 | 23 | protected abstract T map(U next); 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/examples/ApprovalsRuleTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.examples; 2 | 3 | import com.oneeyedmen.okeydoke.junit4.ApprovalsRule; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | 7 | import static com.oneeyedmen.okeydoke.examples.Support.doSomeCalculation; 8 | 9 | public class ApprovalsRuleTest { 10 | 11 | //README_TEXT 12 | @Rule public final ApprovalsRule approver = ApprovalsRule.usualRule(); 13 | 14 | @Test 15 | public void something_that_we_want_to_be_the_same_next_time( 16 | ) { 17 | Object result = doSomeCalculation(42, "banana"); 18 | approver.assertApproved(result); // check that the result is as approved 19 | } 20 | //README_TEXT 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/sources/StreamingFileResource.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.sources; 2 | 3 | import com.oneeyedmen.okeydoke.Resource; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.io.OutputStream; 8 | 9 | public class StreamingFileResource extends FileResource { 10 | 11 | private final Resource comparedTo; 12 | 13 | public StreamingFileResource(File file, Resource comparedTo) { 14 | super(file); 15 | this.comparedTo = comparedTo; 16 | } 17 | 18 | 19 | @Override 20 | protected OutputStream outputStreamFor(File file) throws IOException { 21 | return new ComparingOutputStream(super.outputStream(), comparedTo.inputStream()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Checkers.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import com.oneeyedmen.okeydoke.checkers.BinaryChecker; 4 | import com.oneeyedmen.okeydoke.checkers.HexChecker; 5 | import com.oneeyedmen.okeydoke.checkers.StringChecker; 6 | 7 | public class Checkers { 8 | 9 | private static final Checker string = new StringChecker(); 10 | private static final Checker binary = new BinaryChecker(); 11 | private static final Checker hex = new HexChecker(); 12 | 13 | public static Checker stringChecker() { 14 | return string; 15 | } 16 | 17 | public static Checker binaryChecker() { 18 | return binary; 19 | } 20 | 21 | public static Checker hexChecker() { 22 | return hex; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/reporters/PopupReporter.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.reporters; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | public class PopupReporter extends CommandLineReporter { 7 | 8 | public PopupReporter(String differ) { 9 | super(differ); 10 | } 11 | 12 | @Override 13 | public void reportFailure(File actual, File approved, Throwable e) { 14 | super.reportFailure(actual, approved, e); 15 | try { 16 | Runtime.getRuntime().exec(new String[] { 17 | differ(), 18 | actual.getAbsolutePath(), 19 | approved.getAbsolutePath() }); 20 | } catch (IOException x) { 21 | System.err.println("Failed to run differ : " + x); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/internal/LyingWrappingIterator.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.internal; 2 | 3 | import java.util.Iterator; 4 | 5 | public abstract class LyingWrappingIterator extends WrappingIterator { 6 | 7 | protected int i = 0; 8 | 9 | public LyingWrappingIterator(Iterator wrapped) { 10 | super(wrapped); 11 | } 12 | 13 | @Override 14 | public boolean hasNext() { 15 | return wrapped.hasNext() || hasNext(i); 16 | } 17 | 18 | protected abstract boolean hasNext(int i); 19 | 20 | @Override 21 | public T next() { 22 | try { 23 | return next(i); 24 | } finally { 25 | i++; 26 | } 27 | } 28 | 29 | protected abstract T next(int i); 30 | 31 | @Override 32 | public void remove() { 33 | throw new UnsupportedOperationException(); 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Approver.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | public class Approver extends BaseApprover { 4 | 5 | public Approver(String testName, SourceOfApproval sourceOfApproval) { 6 | this(testName, 7 | sourceOfApproval, 8 | Formatters.stringFormatter(), 9 | Serializers.stringSerializer(), 10 | Checkers.stringChecker() 11 | ); 12 | } 13 | 14 | public Approver(String testName, 15 | SourceOfApproval sourceOfApproval, 16 | Formatter formatter, 17 | Serializer serializer, 18 | Checker checker) { 19 | super(testName, sourceOfApproval, formatter, serializer, checker); 20 | } 21 | 22 | public Transcript transcript() { 23 | return new StandardTranscript(printStream(), formatter()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/internal/DirectoryFinder.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.internal; 2 | 3 | import java.io.File; 4 | 5 | public abstract class DirectoryFinder { 6 | 7 | public static File firstDirThatExists(File... files) { 8 | for (File file : files) { 9 | if (file.isDirectory()) 10 | return file; 11 | } 12 | return null; 13 | } 14 | 15 | private static final File[] likelyDirectories = { 16 | new File("src/test/java"), 17 | new File("src/test/kotlin"), 18 | new File("src/test/scala") 19 | }; 20 | 21 | public static File findARootDirectory() { 22 | File firstDirThatExists = DirectoryFinder.firstDirThatExists(likelyDirectories); 23 | if (firstDirThatExists == null) { 24 | throw new IllegalStateException("Couldn't find a source directory"); 25 | } 26 | return firstDirThatExists; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/serializers/BinarySerializer.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.serializers; 2 | 3 | import com.oneeyedmen.okeydoke.Serializer; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | 10 | public class BinarySerializer implements Serializer { 11 | 12 | @Override 13 | public byte[] readFrom(InputStream is) throws IOException { 14 | ByteArrayOutputStream result = new ByteArrayOutputStream(); 15 | int read = 0; 16 | while ((read = is.read()) != -1) { 17 | result.write(read); 18 | } 19 | return result.toByteArray(); 20 | } 21 | 22 | @Override 23 | public void writeTo(byte[] object, OutputStream os) throws IOException { 24 | os.write(object); 25 | } 26 | 27 | @Override 28 | public byte[] emptyThing() { 29 | return new byte[0]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/junit4/Matchers.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import com.oneeyedmen.okeydoke.Formatter; 4 | import org.hamcrest.Description; 5 | import org.hamcrest.Matcher; 6 | import org.hamcrest.TypeSafeDiagnosingMatcher; 7 | import org.hamcrest.core.IsEqual; 8 | 9 | public class Matchers { 10 | public static Matcher isFormatted(final String formattedExpected, final Formatter formatter) { 11 | return new TypeSafeDiagnosingMatcher() { 12 | @Override 13 | protected boolean matchesSafely(Object item, Description mismatchDescription) { 14 | return new IsEqual(formattedExpected).matches(formatter.formatted(item)); 15 | } 16 | 17 | @Override 18 | public void describeTo(Description description) { 19 | description.appendText("is formatted as ").appendText(formattedExpected); 20 | } 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/internal/IO.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.internal; 2 | 3 | import com.oneeyedmen.okeydoke.Resource; 4 | import com.oneeyedmen.okeydoke.Serializer; 5 | 6 | import java.io.Closeable; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | 10 | public class IO { 11 | 12 | public static T readResource(Resource resource, Serializer serializer) throws IOException { 13 | if (!resource.exists()) 14 | return null; 15 | else { 16 | InputStream input = resource.inputStream(); 17 | try { 18 | return serializer.readFrom(input); 19 | } finally { 20 | closeQuietly(input); 21 | } 22 | } 23 | } 24 | 25 | public static void closeQuietly(Closeable c) { 26 | if (c == null) { 27 | return; 28 | } 29 | try { 30 | c.close(); 31 | } catch (IOException ignored) { 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/checkers/BinaryChecker.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.checkers; 2 | 3 | import com.oneeyedmen.okeydoke.Checker; 4 | import org.opentest4j.AssertionFailedError; 5 | 6 | /** 7 | * A Checker that doesn't render to Hex - it's more efficient but less useful. 8 | */ 9 | public class BinaryChecker implements Checker { 10 | 11 | @Override 12 | public void assertEquals(byte[] expectedMayBeNull, byte[] actualMayBeNull) throws AssertionError { 13 | if (expectedMayBeNull == actualMayBeNull) 14 | return; 15 | if (expectedMayBeNull == null) 16 | throw new AssertionFailedError("Actual was not null", expectedMayBeNull, actualMayBeNull); 17 | 18 | Asserter.assertEquals("Actual has unexpected length", expectedMayBeNull.length, actualMayBeNull.length); 19 | 20 | for (int i = 0; i < expectedMayBeNull.length; i++) { 21 | if (expectedMayBeNull[i] != actualMayBeNull[i]) throw new AssertionFailedError( 22 | "Actual differs from approved at index " + i, expectedMayBeNull[i], actualMayBeNull[i]); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/internal/Fred.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.internal; 2 | 3 | import org.jmock.api.Invocation; 4 | import org.jmock.api.Invokable; 5 | import org.jmock.lib.legacy.ClassImposteriser; 6 | 7 | import java.lang.reflect.InvocationHandler; 8 | 9 | public class Fred { 10 | 11 | private static final org.jmock.api.Imposteriser IMPOSTERISER = loadImposteriser(); 12 | 13 | private static org.jmock.api.Imposteriser loadImposteriser() { 14 | try { 15 | return ClassImposteriser.INSTANCE; 16 | } catch (Throwable t) { 17 | throw new ExceptionInInitializerError("You need JMock and JMock-Legacy in your classpath to do this"); 18 | } 19 | } 20 | 21 | public static T newProxyInstance(Class type, final InvocationHandler handler) 22 | { 23 | Invokable invokable = new Invokable() { 24 | @Override 25 | public Object invoke(Invocation invocation) throws Throwable { 26 | return handler.invoke(null, invocation.getInvokedMethod(), invocation.getParametersAsArray()); 27 | } 28 | }; 29 | return IMPOSTERISER.imposterise(invokable, type); 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/reporters/CommandLineReporter.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.reporters; 2 | 3 | import com.oneeyedmen.okeydoke.Reporter; 4 | 5 | import java.io.File; 6 | 7 | public class CommandLineReporter implements Reporter { 8 | 9 | private final String differ; 10 | 11 | public CommandLineReporter(String differ) { 12 | this.differ = differ; 13 | } 14 | 15 | @Override 16 | public void reportFailure(File actual, File approved, Throwable e) { 17 | reportFailure(actual.getAbsolutePath(), approved.getAbsolutePath()); 18 | } 19 | 20 | protected void reportFailure(String actualPath, String approvedPath) { 21 | System.err.println("To see differences..."); 22 | System.err.println(diffCommandFor(actualPath, approvedPath)); 23 | System.err.println("To approve..."); 24 | System.err.format("cp '%s' '%s'\n", actualPath, approvedPath); 25 | } 26 | 27 | protected String diffCommandFor(String actualPath, String approvedPath) { 28 | return differ() + " '" + actualPath + "' '" + approvedPath + "'"; 29 | } 30 | 31 | protected String differ() { 32 | return differ; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/internal/MethodFinder.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.internal; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class MethodFinder { 8 | 9 | public Class classFor(Object object) { 10 | return object instanceof Class ? (Class) object : object.getClass(); 11 | } 12 | 13 | public List findMethods(Class objectClass, String methodName, Object... arguments) { 14 | List result = new ArrayList(2); 15 | 16 | for (Method method : objectClass.getMethods()) { 17 | if (methodMatches(method, methodName, arguments)) 18 | result.add(method); 19 | } 20 | return result; 21 | } 22 | 23 | protected boolean methodMatches(Method method, String methodName, Object[] arguments) { 24 | if (!method.getName().equals(methodName)) 25 | return false; 26 | return areCompatible(method.getParameterTypes(), arguments); 27 | } 28 | 29 | protected boolean areCompatible(Class[] parameterTypes, Object[] arguments) { 30 | return parameterTypes.length == arguments.length; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/sources/PopupReporterTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.sources; 2 | 3 | import com.oneeyedmen.okeydoke.Approver; 4 | import com.oneeyedmen.okeydoke.Reporters; 5 | import com.oneeyedmen.okeydoke.internal.OperatingSystem; 6 | import com.oneeyedmen.okeydoke.junit4.TestDirectoryRule; 7 | import com.oneeyedmen.okeydoke.reporters.PopupReporter; 8 | import org.junit.Ignore; 9 | import org.junit.Rule; 10 | import org.junit.Test; 11 | 12 | import java.io.IOException; 13 | 14 | public class PopupReporterTest { 15 | 16 | @Rule public final TestDirectoryRule testDirectory = new TestDirectoryRule(); 17 | 18 | private PopupReporter reporter = new PopupReporter(Reporters.differFor(OperatingSystem.current())); 19 | 20 | @Ignore("UnIgnore to try me out") 21 | @Test public void test() throws IOException { 22 | Approver approver = new Approver("testname", 23 | new FileSystemSourceOfApproval(testDirectory.dir(), reporter) 24 | ); 25 | 26 | approver.makeApproved("Now is the time for all good men to come to the aid of the party."); 27 | approver.assertApproved("Now isn't the time for all gods men to come to the aid of the party"); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/formatters/InvocationFormatter.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.formatters; 2 | 3 | import com.oneeyedmen.okeydoke.Formatter; 4 | import com.oneeyedmen.okeydoke.Invocation; 5 | 6 | public class InvocationFormatter implements Formatter { 7 | 8 | public static final String LIST_SEPARATOR = ", "; 9 | private static final int LIST_SEPARATOR_LENGTH = LIST_SEPARATOR.length(); 10 | 11 | @Override 12 | public String formatted(Invocation actual) { 13 | return format(actual.arguments, actual.result); 14 | } 15 | 16 | private String format(Object[] arguments, Object result) { 17 | StringBuilder myResult = new StringBuilder(); 18 | myResult.append("[").append(formatArguments(arguments)).append("] -> "); 19 | myResult.append(String.valueOf(result)); 20 | return myResult.toString(); 21 | } 22 | 23 | protected String formatArguments(Object[] arguments) { 24 | StringBuilder result = new StringBuilder(); 25 | for (Object argument : arguments) { 26 | result.append(String.valueOf(argument)).append(LIST_SEPARATOR); 27 | } 28 | return result.substring(0, result.length() - LIST_SEPARATOR_LENGTH); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Formatters.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import com.oneeyedmen.okeydoke.formatters.BinaryFormatter; 4 | import com.oneeyedmen.okeydoke.formatters.InvocationFormatter; 5 | import com.oneeyedmen.okeydoke.formatters.StringFormatter; 6 | import com.oneeyedmen.okeydoke.formatters.TableFormatter; 7 | 8 | public class Formatters { 9 | 10 | private static final Formatter doubleQuotedString = new StringFormatter("\""); 11 | private static final Formatter binary = new BinaryFormatter(); 12 | private static final Formatter table = new TableFormatter(); 13 | private static final Formatter invocation = new InvocationFormatter(); 14 | 15 | 16 | public static Formatter stringFormatter() { 17 | return doubleQuotedString; 18 | } 19 | 20 | public static Formatter binaryFormatter() { 21 | return binary; 22 | } 23 | 24 | public static Formatter invocationFormatter() { 25 | return invocation; 26 | } 27 | 28 | public static Formatter table() { 29 | // declared as base type to stop mutation with TableFormatter.withXXX methods 30 | return table; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/examples/ApprovalsExtensionTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.examples; 2 | 3 | import com.oneeyedmen.okeydoke.Approver; 4 | import com.oneeyedmen.okeydoke.junit5.ApprovalsExtension; 5 | import org.junit.jupiter.api.Disabled; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | import org.junit.jupiter.api.extension.RegisterExtension; 9 | import org.opentest4j.AssertionFailedError; 10 | 11 | import java.io.IOException; 12 | 13 | import static com.oneeyedmen.okeydoke.examples.Support.doSomeCalculation; 14 | import static org.junit.Assert.assertEquals; 15 | import static org.junit.Assert.fail; 16 | 17 | //README_TEXT 18 | public class ApprovalsExtensionTest { 19 | 20 | // Initialise okey-doke. 21 | @RegisterExtension ApprovalsExtension approvals = new ApprovalsExtension(); 22 | // See other constructors to change where the files are stored, 23 | // or change the extension 24 | 25 | @Test 26 | public void something_that_we_want_to_be_the_same_next_time( 27 | Approver approver // approver will be injected 28 | ) { 29 | Object result = doSomeCalculation(42, "banana"); 30 | approver.assertApproved(result); // check that the result is as approved 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/StringFormatterTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.formatters; 2 | 3 | import com.oneeyedmen.okeydoke.junit4.ApprovalsRule; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | 7 | import java.util.Arrays; 8 | 9 | public class StringFormatterTest { 10 | 11 | @Rule public final ApprovalsRule approver = ApprovalsRule.fileSystemRule("src/test/java"); 12 | 13 | private final StringFormatter formatter = new StringFormatter("\""); 14 | 15 | @Test public void a_string_is_itself() { 16 | approver.assertApproved("A String", formatter); 17 | } 18 | 19 | @Test public void object_uses_toString() { 20 | approver.assertApproved(new StringBuilder("A StringBuilder"), formatter); 21 | } 22 | 23 | @Test public void array_is_listed() { 24 | approver.assertApproved(new String[] {"one", "two", "three"}, formatter); 25 | } 26 | 27 | @Test public void iterable_is_listed() { 28 | approver.assertApproved(Arrays.asList("one", "two", "three"), formatter); 29 | } 30 | 31 | @Test public void null_is_printed() { 32 | approver.assertApproved(null, formatter); 33 | } 34 | 35 | @Test public void emptyIterable() { 36 | approver.assertApproved(new String[] {}, formatter); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Sources.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import com.oneeyedmen.okeydoke.sources.FileSystemSourceOfApproval; 4 | import com.oneeyedmen.okeydoke.sources.StreamingFileSystemSourceOfApproval; 5 | 6 | import java.io.File; 7 | 8 | public class Sources { 9 | 10 | public static FileSystemSourceOfApproval in(File directory) { 11 | return new FileSystemSourceOfApproval(directory, Reporters.fileSystemReporter()); 12 | } 13 | 14 | public static FileSystemSourceOfApproval in(File srcRoot, Package thePackage) { 15 | return in(dirForPackage(srcRoot, thePackage)); 16 | } 17 | 18 | public static FileSystemSourceOfApproval streamingInto(File directory) { 19 | return new StreamingFileSystemSourceOfApproval(directory, Reporters.fileSystemReporter()); 20 | } 21 | 22 | public static FileSystemSourceOfApproval streamingInto(File srcRoot, Package thePackage) { 23 | return streamingInto(dirForPackage(srcRoot, thePackage)); 24 | } 25 | 26 | private static File dirForPackage(File root, Package aPackage) { 27 | return new File(root, pathForPackage(aPackage)); 28 | } 29 | 30 | public static String pathForPackage(Package aPackage) { 31 | return aPackage.getName().replaceAll("\\.", "/"); 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/examples/TranscriptTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.examples; 2 | 3 | import com.oneeyedmen.okeydoke.Transcript; 4 | import com.oneeyedmen.okeydoke.junit4.ApprovalsRule; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | 8 | public class TranscriptTest { 9 | 10 | @Rule public final ApprovalsRule approver = ApprovalsRule.fileSystemRule("src/test/java"); 11 | 12 | @Test 13 | public void can_describe_what_we_are_doing_before_assertion() { 14 | Transcript transcript = approver.transcript(); 15 | transcript.appendLine("As a greengrocer"). 16 | appendLine("I want to sing"); 17 | 18 | String fruit = "banana"; 19 | int fruitCount = 0; 20 | 21 | transcript.appendLine("Given " + fruit + " count " + fruitCount); 22 | transcript.append("I sing ").appendFormatted(new Song(fruit, fruitCount)); 23 | } 24 | 25 | private class Song { 26 | private final String fruit; 27 | private final int count; 28 | 29 | public Song(String fruit, int count) { 30 | this.fruit = fruit; 31 | this.count = count; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return "yes we have " + count + " " + fruit + "(s)"; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/util/DirectoryFinderTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.util; 2 | 3 | import com.oneeyedmen.okeydoke.internal.DirectoryFinder; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | public class DirectoryFinderTest { 13 | 14 | private final File ROOT = new TestDirectory(DirectoryFinderTest.class); 15 | 16 | private final File not_there = new File(ROOT, "not_there"); 17 | private final File thereButAFile = new File(ROOT, "is_a_file"); 18 | private final File there = new File(ROOT, "is_a_dir"); 19 | 20 | @Before 21 | public void setup() throws IOException { 22 | thereButAFile.createNewFile(); 23 | there.mkdirs(); 24 | not_there.delete(); 25 | 26 | assertFalse(not_there.exists()); 27 | assertTrue(thereButAFile.isFile()); 28 | assertTrue(there.isDirectory()); 29 | } 30 | 31 | @Test public void first_existing_dir() throws IOException { 32 | assertEquals(there, DirectoryFinder.firstDirThatExists(not_there, thereButAFile, there)); 33 | 34 | } 35 | 36 | @Test public void returns_null_if_none_found() throws IOException { 37 | assertNull(DirectoryFinder.firstDirThatExists(not_there, thereButAFile)); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/FileSystemApprovalsRuleTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.opentest4j.AssertionFailedError; 6 | 7 | import java.io.IOException; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | import static org.junit.Assert.fail; 11 | 12 | public class FileSystemApprovalsRuleTest { 13 | 14 | @Rule public final ApprovalsRule approver = ApprovalsRule.fileSystemRule("src/test/java"); 15 | 16 | @Test 17 | public void doesnt_match_where_no_approved_result() throws IOException { 18 | try { 19 | approver.assertApproved("banana"); 20 | fail(); 21 | } catch (AssertionError expected) { 22 | } finally { 23 | approver.removeApproved(); 24 | } 25 | } 26 | 27 | @Test public void matches_when_approved_result_matches() { 28 | approver.assertApproved("banana"); 29 | } 30 | 31 | @Test public void doesnt_match_when_approved_result_doesnt_match() { 32 | try { 33 | approver.assertApproved("kumquat"); 34 | fail(); 35 | } catch (AssertionFailedError expected) { 36 | assertEquals("kumquat", expected.getActual().getValue()); 37 | assertEquals("banana", expected.getExpected().getValue()); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/StandardTranscriptTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.OutputStream; 7 | import java.io.PrintStream; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | 11 | public class StandardTranscriptTest { 12 | 13 | private final OutputStream outputStream = new ByteArrayOutputStream(); 14 | 15 | @Test 16 | public void doesnt_use_platform_line_ending() { 17 | String oldSeparator = System.getProperty("line.separator"); 18 | 19 | try { 20 | System.setProperty("line.separator", "***\n"); 21 | Transcript transcript = new StandardTranscript(new PrintStream(outputStream), Formatters.stringFormatter()); 22 | transcript.appendLine("Line 1").append("Line 2").endl(); 23 | } finally { 24 | System.setProperty("line.separator", oldSeparator); 25 | } 26 | 27 | assertEquals("Line 1\nLine 2\n", outputStream.toString()); 28 | } 29 | 30 | @Test 31 | public void uses_given_line_ending() { 32 | Transcript transcript = new StandardTranscript(new PrintStream(outputStream), Formatters.stringFormatter(), "$"); 33 | transcript.appendLine("Line 1").append("Line 2").endl(); 34 | assertEquals("Line 1$Line 2$", outputStream.toString()); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/junit4/BinaryApprovalsRule.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import com.oneeyedmen.okeydoke.ApproverFactories; 4 | import com.oneeyedmen.okeydoke.ApproverFactory; 5 | import com.oneeyedmen.okeydoke.BinaryApprover; 6 | 7 | import java.io.File; 8 | 9 | /** 10 | * Use as an @Rule to automate approvals in JUnit. 11 | */ 12 | public class BinaryApprovalsRule extends BaseApprovalsRule { 13 | 14 | public static BinaryApprovalsRule fileSystemRule(String sourceRoot) { 15 | return new BinaryApprovalsRule(ApproverFactories.binaryFileSystemApproverFactory(new File(sourceRoot))); 16 | } 17 | 18 | public static BinaryApprovalsRule fileSystemRule(String sourceRoot, String extension) { 19 | return new BinaryApprovalsRule(ApproverFactories.binaryFileSystemApproverFactory(new File(sourceRoot), extension)); 20 | } 21 | 22 | public static BinaryApprovalsRule streamingFileSystemRule(final String sourceRoot) { 23 | return ApproverFactories.streamingBinaryApproverFactory(new File(sourceRoot)); 24 | } 25 | 26 | public BinaryApprovalsRule(ApproverFactory factory) { 27 | super(factory); 28 | } 29 | 30 | public BinaryApprovalsRule(ApproverFactory factory, TestNamer testNamer) { 31 | super(factory, testNamer); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/TheoryApprovalsRuleReflectionTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import org.junit.Test; 4 | import org.junit.experimental.theories.Theories; 5 | import org.junit.runner.RunWith; 6 | 7 | import static com.oneeyedmen.okeydoke.junit4.TheoryApprovalsRule.fileSystemRule; 8 | 9 | @RunWith(Theories.class) 10 | public class TheoryApprovalsRuleReflectionTest { 11 | 12 | // Here we show what happens when reflection goes bad 13 | 14 | private final TheoryApprovalsRule.TheoryApprover approver = fileSystemRule("src/test/java").approver(); 15 | 16 | @Test(expected = NoSuchMethodException.class) 17 | public void legacyMethod_output_reflectively_no_such_method_name() throws Exception { 18 | approver.lockDownReflectively(TheoryApprovalsRuleTest.class, "noSuchMethod", "banana", 42); 19 | } 20 | 21 | @Test(expected = NoSuchMethodException.class) 22 | public void legacyMethod_output_reflectively_no_method_with_parameter_count() throws Exception { 23 | approver.lockDownReflectively(TheoryApprovalsRuleTest.class, "legacyMethod", "one", "two", "three"); 24 | } 25 | 26 | @Test(expected = NoSuchMethodException.class) 27 | public void legacyMethod_output_reflectively_wrong_parameter_types() throws Exception { 28 | approver.lockDownReflectively(TheoryApprovalsRuleTest.class, "legacyMethod", 42, "banana"); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/internal/HexDumpTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.oneeyedmen.okeydoke.internal; 5 | 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.*; 9 | 10 | public class HexDumpTest 11 | { 12 | @Test 13 | public void testOneByte() { 14 | assertEquals("00", HexDump.format((byte) 0x00)); 15 | assertEquals("0F", HexDump.format((byte) 0x0F)); 16 | assertEquals("F0", HexDump.format((byte) 0xF0)); 17 | assertEquals("FF", HexDump.format((byte) 0xFF)); 18 | 19 | assertEquals("0x80", HexDump.format((byte) 0x80, true)); 20 | } 21 | 22 | @Test 23 | public void testByteArray() { 24 | assertEquals("00 01 FE FF", 25 | HexDump.format(new byte[] {0x00, 0x01, (byte) 0xFE, (byte) 0xFF})); 26 | assertEquals("0001FEFF", 27 | HexDump.formatCompact(new byte[] {0x00, 0x01, (byte) 0xFE, (byte) 0xFF})); 28 | assertEquals("0x00 0xFF", 29 | HexDump.format(new byte[] {0x00, (byte) 0xFF}, true, true)); 30 | } 31 | 32 | @Test 33 | public void testRead() { 34 | assertArrayEquals( 35 | new byte[]{0x00, 0x01, (byte) 0xFE, (byte) 0xFF}, 36 | HexDump.readCompact("0001FEFF")); 37 | try { 38 | HexDump.readCompact("0001FEF"); 39 | fail(); 40 | } catch (NumberFormatException x) { 41 | } 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/Reporters.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import com.oneeyedmen.okeydoke.internal.OperatingSystem; 4 | import com.oneeyedmen.okeydoke.reporters.CommandLineReporter; 5 | import com.oneeyedmen.okeydoke.reporters.PopupReporter; 6 | 7 | import java.io.File; 8 | 9 | public abstract class Reporters { 10 | 11 | public static final String DIFFER_PROPERTY_NAME = "okeydoke.differ"; 12 | public static final String POPUP_PROPERTY_NAME = "okeydoke.popup"; 13 | 14 | public static Reporter fileSystemReporter() { 15 | return popup() ? new PopupReporter(differ()) : new CommandLineReporter(differ()); 16 | } 17 | 18 | private static boolean popup() { 19 | String propertyValue = System.getProperty(POPUP_PROPERTY_NAME, "false"); 20 | return propertyValue.equals("") ? true : Boolean.valueOf(propertyValue); 21 | } 22 | 23 | private static String differ() { 24 | String override = System.getProperty(DIFFER_PROPERTY_NAME); 25 | return override != null ? override : differFor(OperatingSystem.current()); 26 | } 27 | 28 | public static String differFor(OperatingSystem os) { 29 | switch (os) { 30 | case LINUX: 31 | return "bcompare"; 32 | case MAC: 33 | return "opendiff"; 34 | default: 35 | return "diff"; 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/formatters/TableFormatter.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.formatters; 2 | 3 | import com.oneeyedmen.okeydoke.internal.MappedIterable; 4 | import com.oneeyedmen.okeydoke.internal.Mapper; 5 | import com.oneeyedmen.okeydoke.util.Tabulator; 6 | 7 | public class TableFormatter extends StringFormatter { 8 | 9 | // NB - I've tried making TableFormatter implement Formatter but then it can't cope with arrays. 10 | 11 | private static final Tabulator tabulator = new Tabulator(); 12 | 13 | private String[] headersOrNull; 14 | private Mapper mapperOrNull; 15 | 16 | public TableFormatter() { 17 | super("\""); 18 | } 19 | 20 | public TableFormatter withHeaders(String... headers) { 21 | this.headersOrNull = headers; 22 | return this; 23 | } 24 | 25 | public TableFormatter withMapper(Mapper mapper) { 26 | this.mapperOrNull = mapper; 27 | return this; 28 | } 29 | 30 | @Override 31 | protected String stringFor(Iterable iterable) { 32 | return headersOrNull == null ? 33 | tabulator.tableOf(mappedIterable(iterable)) : 34 | tabulator.headedTableOf(mappedIterable(iterable), headersOrNull); 35 | } 36 | 37 | private Iterable mappedIterable(Iterable iterable) { 38 | return mapperOrNull == null ? iterable : MappedIterable.map(iterable, mapperOrNull); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/ApprovalsRuleTidiesUpTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import com.oneeyedmen.okeydoke.Approver; 4 | import com.oneeyedmen.okeydoke.Sources; 5 | import com.oneeyedmen.okeydoke.testutils.PrecannedApprovalsRule; 6 | import org.junit.Rule; 7 | import org.junit.Test; 8 | import org.junit.rules.RuleChain; 9 | import org.junit.rules.TestWatcher; 10 | import org.junit.runner.Description; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | 15 | import static org.junit.Assert.fail; 16 | 17 | public class ApprovalsRuleTidiesUpTest { 18 | 19 | private final Approver delegate = new Approver("testname", Sources.in(new File("target/approvals"))); 20 | 21 | private final TestWatcher checkDelegateIsCheckedRule = new TestWatcher() { 22 | @Override 23 | protected void succeeded(Description description) { 24 | if (!delegate.satisfactionChecked()) 25 | fail("Rule didn't check delegate's satisfaction"); 26 | } 27 | }; 28 | 29 | private final ApprovalsRule approver = PrecannedApprovalsRule.with(delegate); 30 | 31 | @Rule public final RuleChain rules = RuleChain.outerRule(checkDelegateIsCheckedRule).around(approver); 32 | 33 | @Test public void rule_will_check_satisfaction() throws IOException { 34 | approver.makeApproved("banana"); 35 | 36 | approver.writeFormatted("banana"); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/serializers/StringSerializer.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.serializers; 2 | 3 | import com.oneeyedmen.okeydoke.Serializer; 4 | 5 | import java.io.*; 6 | import java.nio.charset.Charset; 7 | 8 | /** 9 | * The standard Formatter, formats Objects to Strings. 10 | */ 11 | public class StringSerializer implements Serializer { 12 | 13 | private static final int BUFFER_SIZE = 4 * 1024; 14 | 15 | private final Charset charset; 16 | 17 | public StringSerializer(Charset charset) { 18 | this.charset = charset; 19 | } 20 | 21 | @Override 22 | public String readFrom(InputStream is) throws IOException { 23 | return readFully(new InputStreamReader(is, charset)); 24 | } 25 | 26 | @Override 27 | public String emptyThing() { 28 | return ""; 29 | } 30 | 31 | @Override 32 | public void writeTo(String s, OutputStream os) throws IOException { 33 | os.write(s.getBytes(charset)); 34 | } 35 | 36 | public Charset getCharset() { 37 | return charset; 38 | } 39 | 40 | private static String readFully(Reader input) throws IOException { 41 | StringBuilder result = new StringBuilder(); 42 | char[] buffer = new char[BUFFER_SIZE]; 43 | int charsRead; 44 | while ((charsRead = input.read(buffer)) != -1) { 45 | result.append(buffer, 0, charsRead); 46 | } 47 | return result.toString(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/internal/OperatingSystem.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.internal; 2 | 3 | public enum OperatingSystem { 4 | WINDOWS("Windows", "(?i).*win.*"), //$NON-NLS-1$ //$NON-NLS-2$ 5 | LINUX("Linux", "(?i).*lin.*"), //$NON-NLS-1$ //$NON-NLS-2$ 6 | MAC("MacOS", "(?i).*mac.*"); //$NON-NLS-1$ //$NON-NLS-2$ 7 | 8 | private static final OperatingSystem current = 9 | fromString(System.getProperty("os.name")); //$NON-NLS-1$ 10 | 11 | private final String name; 12 | private final String regexp; 13 | 14 | private OperatingSystem(String name, String regexp) { 15 | this.name = name; 16 | this.regexp = regexp; 17 | } 18 | 19 | public static OperatingSystem current() { 20 | return current; 21 | } 22 | 23 | public static OperatingSystem fromString(String propertyValue) { 24 | for (OperatingSystem os : values()) { 25 | if (propertyValue.matches(os.regexp)) 26 | return os; 27 | } 28 | return null; 29 | } 30 | 31 | public String getName() { 32 | return name; 33 | } 34 | 35 | public String toString() { 36 | return name; 37 | } 38 | 39 | public boolean isMyOS() { 40 | return is(this); 41 | } 42 | 43 | public static boolean is(OperatingSystem os) { 44 | return current() == os; 45 | } 46 | 47 | public static boolean isnt(OperatingSystem os) { 48 | return !is(os); 49 | } 50 | } -------------------------------------------------------------------------------- /licence.txt: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | Copyright (c) 2013, www.rococoa.org 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | Redistributions of source code must retain the above copyright notice, this list of 10 | conditions and the following disclaimer. Redistributions in binary form must reproduce 11 | the above copyright notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the distribution. 13 | 14 | Neither the name of Okeydoke nor the names of its contributors may be used to endorse 15 | or promote products derived from this software without specific prior written 16 | permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 19 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 21 | SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 23 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 24 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 26 | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 27 | DAMAGE. 28 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/sources/LazyOutputStream.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.sources; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | /** 7 | * Like a FilterOutputStream, but creates its delegate only when first required. 8 | */ 9 | public abstract class LazyOutputStream extends OutputStream { 10 | 11 | private OutputStream _out = null; 12 | 13 | protected abstract OutputStream createOut() throws IOException; 14 | 15 | private OutputStream out() throws IOException { 16 | if (_out == null) { 17 | _out = createOut(); 18 | } 19 | return _out; 20 | } 21 | 22 | public void write(int b) throws IOException { 23 | out().write(b); 24 | } 25 | 26 | public void write(byte b[]) throws IOException { 27 | write(b, 0, b.length); 28 | } 29 | 30 | public void write(byte b[], int off, int len) throws IOException { 31 | if ((off | len | (b.length - (len + off)) | (off + len)) < 0) 32 | throw new IndexOutOfBoundsException(); 33 | 34 | for (int i = 0 ; i < len ; i++) { 35 | write(b[off + i]); 36 | } 37 | } 38 | 39 | public void flush() throws IOException { 40 | if (_out == null) 41 | return; 42 | out().flush(); 43 | } 44 | 45 | public void close() throws IOException { 46 | if (_out == null) 47 | return; 48 | try { 49 | flush(); 50 | } catch (IOException ignored) { 51 | } 52 | out().close(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/formatters/StringFormatter.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.formatters; 2 | 3 | import com.oneeyedmen.okeydoke.Formatter; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * The standard Formatter, formats Objects to Strings. 9 | */ 10 | public class StringFormatter implements Formatter { 11 | 12 | private static final int BUFFER_SIZE = 4 * 1024; 13 | private final String quoteChar; 14 | 15 | public StringFormatter(String quoteChar) { 16 | this.quoteChar = quoteChar; 17 | } 18 | 19 | @Override 20 | public String formatted(Object actual) { 21 | if (actual == null) 22 | return representationOfNull(); 23 | if (actual.getClass().isArray()) 24 | return stringFor((Object[]) actual); 25 | if (actual instanceof Iterable) 26 | return stringFor((Iterable) actual); 27 | return String.valueOf(actual); 28 | } 29 | 30 | protected String representationOfNull() { 31 | return "NULL"; 32 | } 33 | 34 | protected String stringFor(Iterable iterable) { 35 | StringBuilder result = new StringBuilder("["); 36 | for (Object o : iterable) { 37 | result.append(quoteChar).append(formatted(o)).append(quoteChar + ","); 38 | } 39 | if (result.length() > 1) 40 | result.deleteCharAt(result.length() - 1); 41 | result.append("]"); 42 | return result.toString(); 43 | } 44 | 45 | protected String stringFor(Object[] iterable) { 46 | return stringFor(Arrays.asList(iterable)); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit5/ApprovalsExtensionTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit5; 2 | 3 | import com.oneeyedmen.okeydoke.Approver; 4 | import org.junit.jupiter.api.AfterEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.opentest4j.AssertionFailedError; 8 | 9 | import java.io.File; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertThrows; 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | 14 | @ExtendWith(ApprovalsExtension.class) 15 | public class ApprovalsExtensionTest { 16 | 17 | private static File dir = new File("src/test/java/com/oneeyedmen/okeydoke/junit5/"); 18 | 19 | @AfterEach 20 | public void cleanupFiles() { 21 | new File(dir, "ApprovalsExtensionTest.shouldFailInvalidOutput.actual").delete(); 22 | new File(dir, "ApprovalsExtensionTest.shouldRetainTheActualOutputOnFailure.actual").delete(); 23 | } 24 | 25 | @Test 26 | public void shouldPassValidOutput(Approver approver) { 27 | approver.assertApproved("valid output"); 28 | } 29 | 30 | @Test 31 | public void shouldFailInvalidOutput(Approver approver) { 32 | assertThrows(AssertionFailedError.class, () -> approver.assertApproved("invalid output")); 33 | } 34 | 35 | @Test 36 | public void shouldRetainTheActualOutputOnFailure(Approver approver) { 37 | try { 38 | approver.assertApproved("invalid output"); 39 | } catch (Throwable ignored) { 40 | } 41 | 42 | assertTrue(new File(dir, "ApprovalsExtensionTest.shouldRetainTheActualOutputOnFailure.actual").exists()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/testutils/CleanDirectoryRule.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.testutils; 2 | 3 | import org.junit.rules.ExternalResource; 4 | 5 | import java.io.File; 6 | import java.io.FilenameFilter; 7 | 8 | public class CleanDirectoryRule extends ExternalResource { 9 | 10 | public static final boolean AFTER_TOO = true; 11 | 12 | private final File dir; 13 | private final boolean cleanAfterToo; 14 | 15 | public CleanDirectoryRule(File dir) { 16 | this(dir, false); 17 | } 18 | 19 | public CleanDirectoryRule(File dir, boolean cleanAfterToo) { 20 | this.dir = dir; 21 | this.cleanAfterToo = cleanAfterToo; 22 | } 23 | 24 | @Override 25 | protected void before() throws Throwable { 26 | dir.mkdirs(); 27 | clean(); 28 | } 29 | 30 | @Override 31 | protected void after() { 32 | if (cleanAfterToo) 33 | clean(); 34 | } 35 | 36 | private void clean() { 37 | File[] files = dir.listFiles(new FilenameFilter() { 38 | public boolean accept(File dir, String name) { 39 | return name.endsWith(".approved") || name.endsWith("actual"); 40 | } 41 | }); 42 | for (File file : files) { 43 | file.delete(); 44 | } 45 | } 46 | 47 | public static File dirForPackage(String srcRoot, Object o) { 48 | return new File(new File(srcRoot), packageFor(o).getName().replaceAll("\\.", "/")); 49 | } 50 | 51 | private static Package packageFor(Object o) { 52 | return (o instanceof Class) ? ((Class) o).getPackage() : o.getClass().getPackage(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/BinaryApproverTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import com.oneeyedmen.okeydoke.testutils.CleanDirectoryRule; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.opentest4j.AssertionFailedError; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | import static org.junit.Assert.fail; 13 | 14 | public class BinaryApproverTest { 15 | 16 | @Rule public final CleanDirectoryRule clean = new CleanDirectoryRule(new File("target/approvals")); 17 | private final BinaryApprover approver = new BinaryApprover("testname", Sources.in(new File("target/approvals"))); 18 | 19 | @Test 20 | public void doesnt_match_where_no_approved_result() { 21 | try { 22 | approver.assertApproved("banana".getBytes()); 23 | fail(); 24 | } catch (AssertionFailedError failure) { 25 | } 26 | } 27 | 28 | @Test public void matches_when_approved_result_matches() throws IOException { 29 | approver.makeApproved("banana".getBytes()); 30 | approver.assertApproved("banana".getBytes()); 31 | } 32 | 33 | @Test public void doesnt_match_when_approved_result_doesnt_match() throws IOException { 34 | approver.makeApproved("banana".getBytes()); 35 | try { 36 | approver.assertApproved("bnana".getBytes()); 37 | fail(); 38 | } catch (AssertionFailedError failure) { 39 | assertEquals("62 6E 61 6E 61", failure.getActual().getValue()); 40 | assertEquals("62 61 6E 61 6E 61", failure.getExpected().getValue()); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/junit4/ApprovalsRule.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import com.oneeyedmen.okeydoke.Approver; 4 | import com.oneeyedmen.okeydoke.ApproverFactories; 5 | import com.oneeyedmen.okeydoke.ApproverFactory; 6 | import com.oneeyedmen.okeydoke.Transcript; 7 | 8 | import java.io.File; 9 | 10 | import static com.oneeyedmen.okeydoke.internal.DirectoryFinder.findARootDirectory; 11 | 12 | /** 13 | * Use as an @Rule to automate approvals in JUnit. 14 | */ 15 | public class ApprovalsRule extends BaseApprovalsRule { 16 | 17 | /** Left for backward compatibility **/ 18 | public static final String usualJavaSourceRoot = "src/test/java"; 19 | 20 | public static ApprovalsRule usualRule() { 21 | return fileSystemRule(findARootDirectory()); 22 | } 23 | 24 | public static ApprovalsRule fileSystemRule(String sourceRoot) { 25 | return fileSystemRule(new File(sourceRoot)); 26 | } 27 | 28 | public static ApprovalsRule fileSystemRule(File sourceRoot) { 29 | return new ApprovalsRule(ApproverFactories.fileSystemApproverFactory(sourceRoot)); 30 | } 31 | 32 | public static ApprovalsRule streamingFileSystemRule(String sourceRoot) { 33 | return new ApprovalsRule(ApproverFactories.streamingApproverFactory(new File(sourceRoot))); 34 | } 35 | 36 | public ApprovalsRule(ApproverFactory factory) { 37 | super(factory); 38 | } 39 | 40 | public ApprovalsRule(ApproverFactory factory, TestNamer testNamer) { 41 | super(factory, testNamer); 42 | } 43 | 44 | public Transcript transcript() { 45 | return approver().transcript(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/sources/ComparingOutputStream.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.sources; 2 | 3 | import java.io.FilterOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.OutputStream; 7 | 8 | public class ComparingOutputStream extends FilterOutputStream { 9 | 10 | private final InputStream is; 11 | private long position = -1; 12 | private long firstMismatchPosition = -1; 13 | 14 | public ComparingOutputStream(OutputStream out, InputStream is) { 15 | super(out); 16 | this.is = is; 17 | } 18 | 19 | @Override 20 | public void write(int b) throws IOException { 21 | position++; 22 | if (firstMismatchPosition == -1) { 23 | int read = is.read(); 24 | if (b != read) 25 | firstMismatchPosition = position; 26 | } 27 | super.write(b); 28 | } 29 | 30 | @Override 31 | public void write(byte[] b) throws IOException { 32 | write(b, 0, b.length); 33 | } 34 | 35 | @Override 36 | public void write(byte[] b, int off, int len) throws IOException { 37 | if (firstMismatchPosition == -1) { 38 | for (int i = off; i < len; i++) { 39 | write(b[i]); 40 | } 41 | } else { 42 | super.write(b, off, len); 43 | } 44 | } 45 | 46 | @Override 47 | public void close() throws IOException { 48 | is.close(); 49 | super.close(); 50 | } 51 | 52 | public void assertNoMismatch() throws AssertionError { 53 | if (firstMismatchPosition != -1) 54 | throw new AssertionError("Streams differed at " + firstMismatchPosition); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/sources/FileResource.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.sources; 2 | 3 | import com.oneeyedmen.okeydoke.Resource; 4 | 5 | import java.io.*; 6 | 7 | public class FileResource implements Resource { 8 | 9 | private final File file; 10 | private OutputStream os; 11 | 12 | public FileResource(File file) { 13 | this.file = file; 14 | } 15 | 16 | @Override 17 | public OutputStream outputStream() throws IOException { 18 | if (os == null) { 19 | os = outputStreamFor(file); 20 | } 21 | return os; 22 | } 23 | 24 | @Override 25 | public InputStream inputStream() throws IOException { 26 | // It feels a bit odd that this isn't memoized, but we don't seem to need it to be 27 | return new FileInputStream(file); 28 | } 29 | 30 | @Override 31 | public void remove() throws IOException { 32 | file.delete(); 33 | if (exists()) 34 | throw new IOException("Failed to delete " + file); 35 | } 36 | 37 | @Override 38 | public boolean exists() { 39 | return file.exists(); 40 | } 41 | 42 | @Override 43 | public long size() { 44 | return file.length(); 45 | } 46 | 47 | public File file() { 48 | return file; 49 | } 50 | 51 | protected OutputStream outputStreamFor(final File file) throws IOException { 52 | return new LazyOutputStream() { 53 | @Override 54 | protected OutputStream createOut() throws IOException { 55 | file.getParentFile().mkdirs(); 56 | return new BufferedOutputStream(new FileOutputStream(file)); 57 | } 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/sources/StreamingFileSystemSourceOfApproval.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.sources; 2 | 3 | import com.oneeyedmen.okeydoke.Checker; 4 | import com.oneeyedmen.okeydoke.Reporter; 5 | import com.oneeyedmen.okeydoke.Resource; 6 | import com.oneeyedmen.okeydoke.Serializer; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | 11 | /** 12 | * A SourceOfApproval that compares the actual to the approved as it is writing the actual. 13 | * 14 | * This saves having to read both into memory, improving performance with lots of data, 15 | * at the expense of less good diff reporting. 16 | */ 17 | public class StreamingFileSystemSourceOfApproval extends FileSystemSourceOfApproval { 18 | 19 | public StreamingFileSystemSourceOfApproval(File directory, Reporter reporter) { 20 | super(directory, reporter); 21 | } 22 | 23 | @Override 24 | public Resource actualFor(String testname) { 25 | File file = approvedFileFor(testname); 26 | return file.exists() && file.isFile() 27 | ? new StreamingFileResource(actualFileFor(testname), new FileResource(approvedFileFor(testname))) 28 | : new FileResource(actualFileFor(testname)); 29 | } 30 | 31 | @Override 32 | public void checkActualAgainstApproved(String testName, Serializer serializer, Checker checker) throws IOException { 33 | Resource actual = actualFor(testName); 34 | if (actual instanceof StreamingFileResource) { 35 | ((ComparingOutputStream) actual.outputStream()).assertNoMismatch(); 36 | } else { 37 | super.checkActualAgainstApproved(testName, serializer, checker); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/sources/FileSystemSourceOfApprovalTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.sources; 2 | 3 | import com.oneeyedmen.okeydoke.Reporters; 4 | import com.oneeyedmen.okeydoke.testutils.CleanDirectoryRule; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.io.OutputStream; 11 | 12 | import static org.junit.Assert.*; 13 | 14 | public class FileSystemSourceOfApprovalTest { 15 | 16 | @Rule 17 | public final CleanDirectoryRule cleaner = new CleanDirectoryRule(new File("target/approvals")); 18 | 19 | private final FileSystemSourceOfApproval sourceOfApproval = new FileSystemSourceOfApproval( 20 | new File("target/approvals"), Reporters.fileSystemReporter()); 21 | 22 | // This is mostly currently tested by ApproverFileLifecycleTest 23 | 24 | @Test public void writes_files_in_package() { 25 | assertEquals( 26 | new File("target/approvals", "testname.approved"), 27 | ((FileResource) sourceOfApproval.approvedFor("testname")).file()); 28 | assertEquals( 29 | new File("target/approvals", "testname.actual"), 30 | ((FileResource) sourceOfApproval.actualFor("testname")).file()); 31 | } 32 | 33 | @Test public void doesnt_create_files_until_written_to() throws IOException { 34 | OutputStream stream = sourceOfApproval.actualFor("testname").outputStream(); 35 | assertFalse(((FileResource) sourceOfApproval.actualFor("testname")).file().isFile()); 36 | 37 | stream.write("hello".getBytes()); 38 | assertTrue(((FileResource) sourceOfApproval.actualFor("testname")).file().isFile()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/StreamingApprovalTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import com.oneeyedmen.okeydoke.junit4.ApprovalsRule; 4 | import com.oneeyedmen.okeydoke.util.TestDirectory; 5 | import org.junit.Ignore; 6 | import org.junit.Rule; 7 | import org.junit.Test; 8 | 9 | import java.io.IOException; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | import static org.junit.Assert.fail; 13 | 14 | @Ignore("for now") 15 | public class StreamingApprovalTest { 16 | 17 | private final TestDirectory dir = new TestDirectory(StreamingApprovalTest.class); 18 | @Rule public ApprovalsRule rule = new ApprovalsRule(ApproverFactories.streamingApproverFactory(dir)); 19 | 20 | @Test public void as_approved() throws IOException { 21 | rule.makeApproved("long contents we don't want to read into memory"); 22 | rule.assertApproved("long contents we don't want to read into memory"); 23 | } 24 | 25 | @Test(expected = AssertionError.class) 26 | public void not_approved() { 27 | rule.assertApproved("long contents we don't want to read into memory"); 28 | } 29 | 30 | @Test 31 | public void not_as_approved() throws IOException { 32 | rule.makeApproved("long contents we don't want to read into memory"); 33 | 34 | try { 35 | rule.assertApproved("long CONTENTS we don't want to read into memory"); 36 | fail(); 37 | } catch (AssertionError expected) { 38 | assertEquals("Streams differed at 5", expected.getMessage()); 39 | } 40 | // assertEquals("long CONTENTS we don't want to read into memory", 41 | // (StreamingFileSystemSourceOfApproval) rule.approver()..actualContentOrNull()); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/StandardTestNamerTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import com.oneeyedmen.okeydoke.Name; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | import static org.hamcrest.Matchers.equalTo; 8 | import static org.junit.runner.Description.createTestDescription; 9 | 10 | public class StandardTestNamerTest { 11 | 12 | private TestNamer namer = new StandardTestNamer(); 13 | 14 | @Test 15 | public void can_use_class_and_method_name() { 16 | assertThat(namer.nameFor(createTestDescription(this.getClass(), "methodName")), 17 | equalTo("StandardTestNamerTest.methodName")); 18 | } 19 | 20 | @Test 21 | public void can_use_class_name_and_method_name() { 22 | assertThat(namer.nameFor(createTestDescription("ClassName", "methodName")), 23 | equalTo("ClassName.methodName")); 24 | } 25 | 26 | @Test 27 | public void overrides_with_class_name_annotation() { 28 | assertThat(namer.nameFor(createTestDescription(TestClass.class, "method")), 29 | equalTo("Fruit.method")); 30 | } 31 | 32 | @Test 33 | public void overrides_with_method_name_annotation() { 34 | assertThat(namer.nameFor(createTestDescription(TestClass.class, "namedMethod")), 35 | equalTo("Fruit.banana")); 36 | } 37 | 38 | @Test 39 | public void handles_null_method_name() { 40 | // this seems to be JUnit behaviour 41 | assertThat(namer.nameFor(createTestDescription(this.getClass(), null)), 42 | equalTo("StandardTestNamerTest.null")); 43 | } 44 | 45 | @Name("Fruit") 46 | static class TestClass { 47 | public void method() {} 48 | @Name("banana") public void namedMethod() {} 49 | 50 | } 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit5/ApprovalsExtensionFieldTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit5; 2 | 3 | import com.oneeyedmen.okeydoke.Approver; 4 | import org.junit.jupiter.api.AfterEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.junit.jupiter.api.extension.RegisterExtension; 8 | import org.opentest4j.AssertionFailedError; 9 | 10 | import java.io.File; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertThrows; 13 | import static org.junit.jupiter.api.Assertions.assertTrue; 14 | 15 | public class ApprovalsExtensionFieldTest { 16 | 17 | @RegisterExtension 18 | ApprovalsExtension approvals = new ApprovalsExtension(".extension"); 19 | 20 | private static File dir = new File("src/test/java/com/oneeyedmen/okeydoke/junit5/"); 21 | 22 | @AfterEach 23 | public void cleanupFiles() { 24 | new File(dir, "ApprovalsExtensionFieldTest.shouldFailInvalidOutput.actual.extension").delete(); 25 | new File(dir, "ApprovalsExtensionFieldTest.shouldRetainTheActualOutputOnFailure.actual.extension").delete(); 26 | } 27 | 28 | @Test 29 | public void shouldPassValidOutput(Approver approver) { 30 | approver.assertApproved("valid output"); 31 | } 32 | 33 | @Test 34 | public void shouldFailInvalidOutput(Approver approver) { 35 | assertThrows(AssertionFailedError.class, () -> approver.assertApproved("invalid output")); 36 | } 37 | 38 | @Test 39 | public void shouldRetainTheActualOutputOnFailure(Approver approver) { 40 | try { 41 | approver.assertApproved("invalid output"); 42 | } catch (Throwable ignored) { 43 | } 44 | 45 | assertTrue(new File(dir, "ApprovalsExtensionFieldTest.shouldRetainTheActualOutputOnFailure.actual.extension").exists()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/internal/HexDump.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.internal; 2 | 3 | 4 | public class HexDump { 5 | 6 | final static private char[] DIGITS = new char[] { 7 | '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , 8 | '8' , '9' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' 9 | }; 10 | 11 | public static String format(byte[] bytes) { 12 | return format(bytes, true, false); 13 | } 14 | 15 | public static String formatCompact(byte[] bytes) { 16 | return format(bytes, false, false); 17 | } 18 | 19 | public static String format(byte[] bytes, boolean spaces, boolean Oxs) { 20 | StringBuilder result = new StringBuilder(); 21 | for (int i = 0, length = bytes.length; i < length; i++) { 22 | if (spaces && i != 0) 23 | result.append(' '); 24 | result.append(format(bytes[i], Oxs)); 25 | } 26 | return result.toString(); 27 | } 28 | 29 | public static String format(byte aByte, boolean Ox) { 30 | char top = DIGITS[ (aByte >> 4) & 0x0F ]; 31 | char bottom = DIGITS[ aByte & 0x0F ]; 32 | return (Ox ? "0x" : "") + top + bottom; 33 | } 34 | 35 | public static String format(byte aByte) { 36 | return format(aByte, false); 37 | } 38 | 39 | public static byte[] readCompact(String hexString) { 40 | if (hexString.length() % 2 != 0) { 41 | throw new NumberFormatException("Hex string " + hexString + " has odd character"); 42 | } 43 | int resultLength = hexString.length() / 2; 44 | byte[] result = new byte[resultLength]; 45 | for (int i = 0; i < resultLength; i++) { 46 | String pair = hexString.substring(2 * i, 2 * i + 2); 47 | result[i] = (byte) Short.parseShort(pair, 16); 48 | } 49 | return result; 50 | } 51 | } -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/checkers/AsserterTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.checkers; 2 | 3 | import org.junit.Test; 4 | import org.opentest4j.AssertionFailedError; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | import static org.junit.Assert.assertNull; 8 | 9 | public class AsserterTest { 10 | 11 | @Test public void passes_for_null_and_null() { 12 | Asserter.assertEquals("message", null, null); 13 | } 14 | 15 | @Test public void passes_for_equal_strings() { 16 | Asserter.assertEquals("message", "banana", "banana"); 17 | } 18 | 19 | @Test public void fails_for_null_and_string() { 20 | try { 21 | Asserter.assertEquals("message", null, "banana"); 22 | } catch (AssertionFailedError exception) { 23 | assertEquals("message", exception.getMessage()); 24 | assertNull(exception.getExpected().getValue()); 25 | assertEquals("banana", exception.getActual().getValue()); 26 | } 27 | } 28 | 29 | @Test public void fails_for_string_and_null() { 30 | try { 31 | Asserter.assertEquals("message", "banana", null); 32 | } catch (AssertionFailedError exception) { 33 | assertEquals("message", exception.getMessage()); 34 | assertEquals("banana", exception.getExpected().getValue()); 35 | assertNull(exception.getActual().getValue()); 36 | } 37 | } 38 | 39 | @Test public void fails_for_string_and_a_different_string() { 40 | try { 41 | Asserter.assertEquals("message", "banana", "kumquat"); 42 | } catch (AssertionFailedError exception) { 43 | assertEquals("message", exception.getMessage()); 44 | assertEquals("banana", exception.getExpected().getValue()); 45 | assertEquals("kumquat", exception.getActual().getValue()); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/junit4/StandardTestNamer.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import com.oneeyedmen.okeydoke.Name; 4 | import org.junit.runner.Description; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class StandardTestNamer implements TestNamer { 9 | 10 | @Override 11 | public String nameFor(Description description) { 12 | return nameFromClass(description) + suffixFromMethod(description); 13 | } 14 | 15 | private String suffixFromMethod(Description description) { 16 | String override = nameFromMethodAnnotation(description); 17 | if (override != null) 18 | return "." + override; 19 | String methodNameFromDescription = description.getMethodName(); 20 | return methodNameFromDescription == null ? "" : ("." + methodNameFromDescription); 21 | } 22 | 23 | private String nameFromMethodAnnotation(Description description) { 24 | Method method = methodFrom(description); 25 | if (method == null) 26 | return null; 27 | Name annotation = method.getAnnotation(Name.class); 28 | return annotation == null ? null : annotation.value(); 29 | } 30 | 31 | private Method methodFrom(Description description) { 32 | Class testClass = description.getTestClass(); 33 | String methodName = description.getMethodName(); 34 | if (testClass == null || methodName == null) 35 | return null; 36 | try { 37 | return testClass.getMethod(methodName); 38 | } catch (NoSuchMethodException e) { 39 | return null; 40 | } 41 | } 42 | 43 | private String nameFromClass(Description description) { 44 | Class testClass = description.getTestClass(); 45 | if (testClass == null) 46 | return description.getClassName(); 47 | Name annotation = testClass.getAnnotation(Name.class); 48 | return annotation == null ? testClass.getSimpleName() : annotation.value(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/StandardTranscript.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import java.io.PrintStream; 4 | 5 | public class StandardTranscript implements Transcript { 6 | 7 | private final PrintStream stream; 8 | private final Formatter formatter; 9 | private final String lineSeparator; 10 | 11 | private boolean isStartOfLine = true; 12 | 13 | public StandardTranscript(PrintStream stream, Formatter formatter) { 14 | this(stream, formatter, "\n"); 15 | } 16 | 17 | 18 | public StandardTranscript(PrintStream stream, Formatter formatter, String lineSeparator) { 19 | this.stream = stream; 20 | this.formatter = formatter; 21 | this.lineSeparator = lineSeparator; 22 | } 23 | 24 | @Override public boolean isStartOfLine() { 25 | return isStartOfLine; 26 | } 27 | 28 | @Override 29 | public Transcript appendLine(String s) { 30 | stream.print(s); 31 | endl(); 32 | isStartOfLine = true; 33 | return this; 34 | } 35 | 36 | @Override 37 | public Transcript append(String s) { 38 | stream.append(s); 39 | isStartOfLine = s.endsWith(lineSeparator); 40 | return this; 41 | } 42 | 43 | @Override 44 | public Transcript appendFormatted(Object o) { 45 | appendFormatted(o, formatter); 46 | return this; 47 | } 48 | 49 | @Override 50 | public Transcript appendFormatted(T o, Formatter aFormatter) { 51 | append(aFormatter.formatted(o)); 52 | return this; 53 | } 54 | 55 | @Override 56 | public Transcript endl() { 57 | stream.print(lineSeparator); 58 | isStartOfLine = true; 59 | return this; 60 | } 61 | 62 | @Override 63 | public Transcript space() { 64 | return append(" "); 65 | } 66 | 67 | @Override 68 | public Transcript space(int number) { 69 | for (int i = 0; i < number; i++) { 70 | space(); 71 | } 72 | return this; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/examples/LockDownTest.legacyMethod_output.approved: -------------------------------------------------------------------------------- 1 | [apple, bear, -1] -> -1 apple eating bears 2 | [apple, bear, 0] -> 0 apple eating bears 3 | [apple, bear, 1] -> 1 apple eating bears 4 | [apple, bear, 2] -> 2 apple eating bears 5 | [apple, bear, 42] -> 42 apple eating bears 6 | [apple, cat, -1] -> -1 apple eating cats 7 | [apple, cat, 0] -> 0 apple eating cats 8 | [apple, cat, 1] -> 1 apple eating cats 9 | [apple, cat, 2] -> 2 apple eating cats 10 | [apple, cat, 42] -> 42 apple eating cats 11 | [apple, dog, -1] -> -1 apple eating dogs 12 | [apple, dog, 0] -> 0 apple eating dogs 13 | [apple, dog, 1] -> 1 apple eating dogs 14 | [apple, dog, 2] -> 2 apple eating dogs 15 | [apple, dog, 42] -> 42 apple eating dogs 16 | [banana, bear, -1] -> -1 banana eating bears 17 | [banana, bear, 0] -> 0 banana eating bears 18 | [banana, bear, 1] -> 1 banana eating bears 19 | [banana, bear, 2] -> 2 banana eating bears 20 | [banana, bear, 42] -> 42 banana eating bears 21 | [banana, cat, -1] -> -1 banana eating cats 22 | [banana, cat, 0] -> 0 banana eating cats 23 | [banana, cat, 1] -> 1 banana eating cats 24 | [banana, cat, 2] -> 2 banana eating cats 25 | [banana, cat, 42] -> 42 banana eating cats 26 | [banana, dog, -1] -> -1 banana eating dogs 27 | [banana, dog, 0] -> 0 banana eating dogs 28 | [banana, dog, 1] -> 1 banana eating dogs 29 | [banana, dog, 2] -> 2 banana eating dogs 30 | [banana, dog, 42] -> 42 banana eating dogs 31 | [kumquat, bear, -1] -> -1 kumquat eating bears 32 | [kumquat, bear, 0] -> 0 kumquat eating bears 33 | [kumquat, bear, 1] -> 1 kumquat eating bears 34 | [kumquat, bear, 2] -> 2 kumquat eating bears 35 | [kumquat, bear, 42] -> 42 kumquat eating bears 36 | [kumquat, cat, -1] -> -1 kumquat eating cats 37 | [kumquat, cat, 0] -> 0 kumquat eating cats 38 | [kumquat, cat, 1] -> 1 kumquat eating cats 39 | [kumquat, cat, 2] -> 2 kumquat eating cats 40 | [kumquat, cat, 42] -> 42 kumquat eating cats 41 | [kumquat, dog, -1] -> -1 kumquat eating dogs 42 | [kumquat, dog, 0] -> 0 kumquat eating dogs 43 | [kumquat, dog, 1] -> 1 kumquat eating dogs 44 | [kumquat, dog, 2] -> 2 kumquat eating dogs 45 | [kumquat, dog, 42] -> 42 kumquat eating dogs 46 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/examples/LockDownTest.legacyMethod_checked_fluently.approved: -------------------------------------------------------------------------------- 1 | [apple, bear, -1] -> -1 apple eating bears 2 | [apple, bear, 0] -> 0 apple eating bears 3 | [apple, bear, 1] -> 1 apple eating bears 4 | [apple, bear, 2] -> 2 apple eating bears 5 | [apple, bear, 42] -> 42 apple eating bears 6 | [apple, cat, -1] -> -1 apple eating cats 7 | [apple, cat, 0] -> 0 apple eating cats 8 | [apple, cat, 1] -> 1 apple eating cats 9 | [apple, cat, 2] -> 2 apple eating cats 10 | [apple, cat, 42] -> 42 apple eating cats 11 | [apple, dog, -1] -> -1 apple eating dogs 12 | [apple, dog, 0] -> 0 apple eating dogs 13 | [apple, dog, 1] -> 1 apple eating dogs 14 | [apple, dog, 2] -> 2 apple eating dogs 15 | [apple, dog, 42] -> 42 apple eating dogs 16 | [banana, bear, -1] -> -1 banana eating bears 17 | [banana, bear, 0] -> 0 banana eating bears 18 | [banana, bear, 1] -> 1 banana eating bears 19 | [banana, bear, 2] -> 2 banana eating bears 20 | [banana, bear, 42] -> 42 banana eating bears 21 | [banana, cat, -1] -> -1 banana eating cats 22 | [banana, cat, 0] -> 0 banana eating cats 23 | [banana, cat, 1] -> 1 banana eating cats 24 | [banana, cat, 2] -> 2 banana eating cats 25 | [banana, cat, 42] -> 42 banana eating cats 26 | [banana, dog, -1] -> -1 banana eating dogs 27 | [banana, dog, 0] -> 0 banana eating dogs 28 | [banana, dog, 1] -> 1 banana eating dogs 29 | [banana, dog, 2] -> 2 banana eating dogs 30 | [banana, dog, 42] -> 42 banana eating dogs 31 | [kumquat, bear, -1] -> -1 kumquat eating bears 32 | [kumquat, bear, 0] -> 0 kumquat eating bears 33 | [kumquat, bear, 1] -> 1 kumquat eating bears 34 | [kumquat, bear, 2] -> 2 kumquat eating bears 35 | [kumquat, bear, 42] -> 42 kumquat eating bears 36 | [kumquat, cat, -1] -> -1 kumquat eating cats 37 | [kumquat, cat, 0] -> 0 kumquat eating cats 38 | [kumquat, cat, 1] -> 1 kumquat eating cats 39 | [kumquat, cat, 2] -> 2 kumquat eating cats 40 | [kumquat, cat, 42] -> 42 kumquat eating cats 41 | [kumquat, dog, -1] -> -1 kumquat eating dogs 42 | [kumquat, dog, 0] -> 0 kumquat eating dogs 43 | [kumquat, dog, 1] -> 1 kumquat eating dogs 44 | [kumquat, dog, 2] -> 2 kumquat eating dogs 45 | [kumquat, dog, 42] -> 42 kumquat eating dogs 46 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/examples/LockDownTest.legacyMethod_checked_reflectively.approved: -------------------------------------------------------------------------------- 1 | [apple, bear, -1] -> -1 apple eating bears 2 | [apple, bear, 0] -> 0 apple eating bears 3 | [apple, bear, 1] -> 1 apple eating bears 4 | [apple, bear, 2] -> 2 apple eating bears 5 | [apple, bear, 42] -> 42 apple eating bears 6 | [apple, cat, -1] -> -1 apple eating cats 7 | [apple, cat, 0] -> 0 apple eating cats 8 | [apple, cat, 1] -> 1 apple eating cats 9 | [apple, cat, 2] -> 2 apple eating cats 10 | [apple, cat, 42] -> 42 apple eating cats 11 | [apple, dog, -1] -> -1 apple eating dogs 12 | [apple, dog, 0] -> 0 apple eating dogs 13 | [apple, dog, 1] -> 1 apple eating dogs 14 | [apple, dog, 2] -> 2 apple eating dogs 15 | [apple, dog, 42] -> 42 apple eating dogs 16 | [banana, bear, -1] -> -1 banana eating bears 17 | [banana, bear, 0] -> 0 banana eating bears 18 | [banana, bear, 1] -> 1 banana eating bears 19 | [banana, bear, 2] -> 2 banana eating bears 20 | [banana, bear, 42] -> 42 banana eating bears 21 | [banana, cat, -1] -> -1 banana eating cats 22 | [banana, cat, 0] -> 0 banana eating cats 23 | [banana, cat, 1] -> 1 banana eating cats 24 | [banana, cat, 2] -> 2 banana eating cats 25 | [banana, cat, 42] -> 42 banana eating cats 26 | [banana, dog, -1] -> -1 banana eating dogs 27 | [banana, dog, 0] -> 0 banana eating dogs 28 | [banana, dog, 1] -> 1 banana eating dogs 29 | [banana, dog, 2] -> 2 banana eating dogs 30 | [banana, dog, 42] -> 42 banana eating dogs 31 | [kumquat, bear, -1] -> -1 kumquat eating bears 32 | [kumquat, bear, 0] -> 0 kumquat eating bears 33 | [kumquat, bear, 1] -> 1 kumquat eating bears 34 | [kumquat, bear, 2] -> 2 kumquat eating bears 35 | [kumquat, bear, 42] -> 42 kumquat eating bears 36 | [kumquat, cat, -1] -> -1 kumquat eating cats 37 | [kumquat, cat, 0] -> 0 kumquat eating cats 38 | [kumquat, cat, 1] -> 1 kumquat eating cats 39 | [kumquat, cat, 2] -> 2 kumquat eating cats 40 | [kumquat, cat, 42] -> 42 kumquat eating cats 41 | [kumquat, dog, -1] -> -1 kumquat eating dogs 42 | [kumquat, dog, 0] -> 0 kumquat eating dogs 43 | [kumquat, dog, 1] -> 1 kumquat eating dogs 44 | [kumquat, dog, 2] -> 2 kumquat eating dogs 45 | [kumquat, dog, 42] -> 42 kumquat eating dogs 46 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/examples/LockDownTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.examples; 2 | 3 | import com.oneeyedmen.okeydoke.junit4.TheoryApprovalsRule; 4 | import org.junit.ClassRule; 5 | import org.junit.Rule; 6 | import org.junit.experimental.theories.DataPoints; 7 | import org.junit.experimental.theories.Theories; 8 | import org.junit.experimental.theories.Theory; 9 | import org.junit.runner.RunWith; 10 | 11 | import java.lang.reflect.InvocationTargetException; 12 | 13 | import static com.oneeyedmen.okeydoke.junit4.TheoryApprovalsRule.fileSystemRule; 14 | 15 | /** 16 | * Here we show how to use Theories and DataPoints to push all combinations of parameters into an approver 17 | * and then lockDown the results. 18 | */ 19 | @RunWith(Theories.class) 20 | public class LockDownTest { 21 | 22 | //README_TEXT 23 | 24 | @ClassRule public static final TheoryApprovalsRule theoryRule = fileSystemRule("src/test/java"); 25 | @Rule public final TheoryApprovalsRule.TheoryApprover approver = theoryRule.approver(); 26 | 27 | @DataPoints public static final Fruit[] FRUITS = Fruit.values(); 28 | @DataPoints public static final Animal[] ANIMALS = Animal.values(); 29 | @DataPoints public static final int[] INTS = { -1, 0, 1, 2, 42 }; 30 | 31 | @Theory 32 | public void legacyMethod_checked_reflectively(Fruit fruit, Animal animal, int i) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { 33 | approver.lockDownReflectively(this, "legacyMethod", fruit.name(), animal.name(), i); 34 | } 35 | 36 | @Theory 37 | public void legacyMethod_checked_fluently(Fruit fruit, Animal animal, int i) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { 38 | approver.lockDown(this).legacyMethod(fruit.name(), animal.name(), i); 39 | } 40 | 41 | public String legacyMethod(String fruitName, String animalName, int i) { 42 | return String.format("%s %s eating %ss", i, fruitName, animalName); 43 | } 44 | 45 | private static enum Fruit { 46 | apple, banana, kumquat 47 | } 48 | 49 | private static enum Animal { 50 | bear, cat, dog 51 | } 52 | 53 | //README_TEXT 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/util/TestDirectoryTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.oneeyedmen.okeydoke.util; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.File; 11 | import java.io.FileReader; 12 | import java.io.IOException; 13 | 14 | import static org.junit.Assert.*; 15 | 16 | public class TestDirectoryTest { 17 | 18 | private final String dirname = "testDir"; 19 | 20 | @Before public void setUp() throws IOException { 21 | new TestDirectory(dirname).delete(); 22 | } 23 | 24 | @Test public void testCreate() { 25 | assertFalse(TestDirectory.fileFor(dirname).exists()); 26 | TestDirectory dir = new TestDirectory(dirname); 27 | assertTrue(TestDirectory.fileFor(dirname).exists()); 28 | assertTrue(dir.exists()); 29 | } 30 | 31 | @Test public void testRemove() throws IOException { 32 | TestDirectory dir = new TestDirectory(dirname); 33 | assertTrue(dir.exists()); 34 | File file = new File(dir, "sub/sub/sub/file"); 35 | assertFalse(file.exists()); 36 | createFile(file); 37 | assertTrue(file.exists()); 38 | dir.remove(); 39 | assertFalse(file.exists()); 40 | assertFalse(dir.exists()); 41 | } 42 | 43 | @Test public void testEmptyOnCreate() throws IOException { 44 | TestDirectory dir = new TestDirectory(dirname); 45 | File file = new File(dir, "sub/sub/sub/file"); 46 | createFile(file); 47 | dir = new TestDirectory(dirname); 48 | assertTrue(dir.exists()); 49 | assertFalse(file.exists()); 50 | } 51 | 52 | @Test public void testCreateFileFrom() throws IOException { 53 | TestDirectory dir = new TestDirectory(dirname); 54 | File file = dir.createFileFrom("temp.txt", "Now is the time etc"); 55 | 56 | BufferedReader r = new BufferedReader(new FileReader(file)); 57 | assertEquals("Now is the time etc", r.readLine()); 58 | r.close(); 59 | } 60 | 61 | private void createFile(File file) throws IOException { 62 | new File(file.getParent()).mkdirs(); 63 | file.createNewFile(); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/formatters/TableFormatterTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.formatters; 2 | 3 | import com.oneeyedmen.okeydoke.Formatters; 4 | import com.oneeyedmen.okeydoke.internal.Mapper; 5 | import com.oneeyedmen.okeydoke.junit4.ApprovalsRule; 6 | import org.junit.Rule; 7 | import org.junit.Test; 8 | 9 | import static java.util.Arrays.asList; 10 | 11 | public class TableFormatterTest { 12 | 13 | @Rule public final ApprovalsRule approver = ApprovalsRule.fileSystemRule("src/test/java"); 14 | 15 | @Test public void array_is_listed_by_row() { 16 | approver.assertApproved(new String[] {"one", "two", "three"}, Formatters.table()); 17 | } 18 | 19 | @Test public void array_table_is_laid_out() { 20 | String [][] data = { 21 | {"one", "two", "three"}, 22 | {"four", "five", "siiiiiiix"}}; 23 | approver.assertApproved(data, Formatters.table()); 24 | } 25 | 26 | @Test public void iterable_is_listed_by_row() { 27 | approver.assertApproved(asList("one", "two", "three"), Formatters.table()); 28 | } 29 | 30 | @Test public void iterable_table_is_laid_out() { 31 | Iterable data = asList( 32 | asList("one", "two", "three"), 33 | asList("four", "five", "siiiiiiix")); 34 | approver.assertApproved(data, Formatters.table()); 35 | } 36 | 37 | @Test public void null_is_printed() { 38 | approver.assertApproved(null); 39 | } 40 | 41 | @Test public void with_header() { 42 | Iterable data = asList( 43 | asList("one", "two", "three"), 44 | asList("four", "five", "siiiiiiix")); 45 | approver.assertApproved(data, new TableFormatter().withHeaders("Header 1", "Header 2", "Header 3")); 46 | } 47 | 48 | @Test public void with_mapper() { 49 | Iterable data = asList("one", "two", "three"); 50 | Mapper mapper = new Mapper() { 51 | @Override public Object[] map(String next) { 52 | return new Object[] {next, next.length()}; 53 | } 54 | }; 55 | approver.assertApproved(data, new TableFormatter().withHeaders("String", "Length").withMapper(mapper)); 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /create_readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | function write_file_contents { 5 | echo '```java' 6 | sed -e '1,/README_TEXT/d' -e '/README_TEXT/,$d' $1 7 | echo '```' 8 | } 9 | 10 | echo " 11 | [![Download](https://maven-badges.herokuapp.com/maven-central/com.oneeyedmen/okeydoke/badge.svg?style=flat-square 12 | )](https://search.maven.org/artifact/com.oneeyedmen/okeydoke) 13 | 14 | okey-doke 15 | ========= 16 | 17 | An Approval Testing library for Java and JUnit - like [Llewellyn Falco's](http://approvaltests.sourceforge.net/) but more Java'y. 18 | 19 | A [helping hand](http://youtu.be/EbqaxWjIgOg) for many testing problems. 20 | 21 | ## Version 2 22 | 23 | If you are upgrading from verions 1.x to version 2.x - JUnit 4 support has been moved from \`com.oneeyedmen.junit\` to \`com.oneeyedmen.junit4\`. 24 | 25 | In return, you no longer need to specify whether your are using Java or Kotlin 26 | 27 | ## JUnit 5 28 | 29 | " > README.md 30 | 31 | write_file_contents src/test/java/com/oneeyedmen/okeydoke/examples/ApprovalsExtensionTest.java >> README.md 32 | 33 | echo " 34 | 35 | The first time you run this test it will fail, but the result of \`doSomeCalculation\` will be written into a 36 | file next to the test, named \`ApprovalsExtensionTest.something_that_we_want_to_be_the_same_next_time.actual\` 37 | 38 | You can look at this file to check that it is what you expect, and if it is, approve the test by renaming the file 39 | it to \`ApprovalsExtensionTest.something_that_we_want_to_be_the_same_next_time.approved\` (or ask the plugin to do it for you). 40 | 41 | From then on the test will pass provided the result of \`doSomeCalculation\` doesn't change. 42 | If it does change then you can either fix the code if it shouldn't have, or approve the new version. 43 | 44 | ## IntelliJ 45 | 46 | There is an [IntelliJ plugin](https://github.com/s4nchez/okey-doke-idea) (thanks @s4nchez) to help approve your output. 47 | 48 | ## JUnit 4 49 | 50 | We still support JUnit 4 with a Rule - 51 | [ApprovalsRuleTest](src/test/java/com/oneeyedmen/okeydoke/examples/ApprovalsRuleTest.java) 52 | " >> README.md 53 | 54 | write_file_contents src/test/java/com/oneeyedmen/okeydoke/examples/ApprovalsRuleTest.java >> README.md 55 | 56 | 57 | echo " 58 | Here you can use the \`ApprovalsRule\` as a approver. 59 | " >> README.md -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/BinaryApproverWithBinaryCheckerTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import com.oneeyedmen.okeydoke.testutils.CleanDirectoryRule; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.opentest4j.AssertionFailedError; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | import static org.junit.Assert.fail; 13 | 14 | public class BinaryApproverWithBinaryCheckerTest { 15 | 16 | @Rule public final CleanDirectoryRule clean = new CleanDirectoryRule(new File("target/approvals")); 17 | 18 | private final BaseApprover approver = new BaseApprover<>( 19 | "testname", 20 | Sources.in(new File("target/approvals")), 21 | Formatters.binaryFormatter(), 22 | Serializers.binarySerializer(), 23 | Checkers.binaryChecker() 24 | ); 25 | 26 | @Test 27 | public void doesnt_match_where_no_approved_result() { 28 | try { 29 | approver.assertApproved("banana".getBytes()); 30 | fail(); 31 | } catch (AssertionFailedError failure) { 32 | } 33 | } 34 | 35 | @Test public void matches_when_approved_result_matches() throws IOException { 36 | approver.makeApproved("banana".getBytes()); 37 | approver.assertApproved("banana".getBytes()); 38 | } 39 | 40 | @Test public void doesnt_match_when_approved_result_doesnt_match_size() throws IOException { 41 | approver.makeApproved("banana".getBytes()); 42 | try { 43 | approver.assertApproved("bnana".getBytes()); 44 | fail(); 45 | } catch (AssertionFailedError failure) { 46 | assertEquals("Actual has unexpected length", failure.getMessage()); 47 | assertEquals(6, failure.getExpected().getValue()); 48 | assertEquals(5, failure.getActual().getValue()); 49 | } 50 | } 51 | 52 | @Test public void doesnt_match_when_approved_result_doesnt_match_content() throws IOException { 53 | approver.makeApproved("banana".getBytes()); 54 | try { 55 | approver.assertApproved("bananb".getBytes()); 56 | fail(); 57 | } catch (AssertionFailedError failure) { 58 | assertEquals("Actual differs from approved at index 5", failure.getMessage()); 59 | assertEquals((byte) 97, failure.getExpected().getValue()); 60 | assertEquals((byte) 98, failure.getActual().getValue()); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/junit4/TheoryApprovalsRuleTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import org.junit.ClassRule; 4 | import org.junit.Rule; 5 | import org.junit.experimental.theories.DataPoints; 6 | import org.junit.experimental.theories.Theories; 7 | import org.junit.experimental.theories.Theory; 8 | import org.junit.runner.RunWith; 9 | 10 | import java.lang.reflect.InvocationTargetException; 11 | 12 | import static com.oneeyedmen.okeydoke.junit4.TheoryApprovalsRule.fileSystemRule; 13 | 14 | @RunWith(Theories.class) 15 | public class TheoryApprovalsRuleTest { 16 | 17 | @ClassRule public static final TheoryApprovalsRule theoryRule = fileSystemRule("src/test/java"); 18 | @Rule public final TheoryApprovalsRule.TheoryApprover approver = theoryRule.approver(); 19 | 20 | @DataPoints public static final String[] FRUITS = { "apple", "banana", "cucumber" }; 21 | 22 | @Theory 23 | public void string_length(String s) { 24 | approver.lockDownResult(s.length(), s); 25 | } 26 | 27 | @Theory 28 | public void legacyMethod_output(String s) { 29 | approver.lockDownResult(legacyMethod(s, s.length()), s, s.length()); 30 | } 31 | 32 | @Theory 33 | public void legacyMethod_output_reflectively(String s) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { 34 | approver.lockDownReflectively(this, "legacyMethod", s, s.length()); 35 | } 36 | 37 | @Theory 38 | public void legacyMethod_output_reflectively_overloaded(String s) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { 39 | approver.lockDownReflectively(this, "legacyMethod", s); 40 | } 41 | 42 | @Theory 43 | public void legacyMethod_output_reflectively_overloaded2(String s) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { 44 | approver.lockDownReflectively(this, "legacyMethod", s, s); 45 | } 46 | 47 | @Theory 48 | public void can_pass_class_for_static_methods(String s) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { 49 | approver.lockDownReflectively(this.getClass(), "legacyMethod", s, s.length()); 50 | } 51 | 52 | public static String legacyMethod(String s, int i) { 53 | return s + i; 54 | } 55 | 56 | public static String legacyMethod(String s1, Object s2) { 57 | return s1 + s2; 58 | } 59 | 60 | public static String legacyMethod(String s) { 61 | return s; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/ApproverTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import com.oneeyedmen.okeydoke.testutils.CleanDirectoryRule; 4 | import org.junit.Ignore; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.opentest4j.AssertionFailedError; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | 12 | import static org.junit.Assert.*; 13 | 14 | public class ApproverTest { 15 | 16 | @Rule public final CleanDirectoryRule cleaner = new CleanDirectoryRule(new File("target/approvals")); 17 | 18 | /* 19 | Note that you won't usually use an Approver directly - ApprovalsRule will manage it for you 20 | */ 21 | private final Approver approver = new Approver("testname", Sources.in(new File("target/approvals"))); 22 | 23 | @Test 24 | public void doesnt_match_where_no_approved_result() throws IOException { 25 | whenApprovedIs(null); 26 | try { 27 | approver.assertApproved("kumquat"); 28 | fail("should have thrown"); 29 | } catch (AssertionFailedError exception) { 30 | assertNull(exception.getExpected().getValue()); 31 | assertEquals("kumquat", exception.getActual().getStringRepresentation()); 32 | } 33 | } 34 | 35 | @Test public void matches_when_approved_result_matches() throws IOException { 36 | whenApprovedIs("banana"); 37 | approver.assertApproved("banana"); 38 | } 39 | 40 | @Test public void doesnt_match_when_approved_result_doesnt_match() throws IOException { 41 | whenApprovedIs("banana"); 42 | try { 43 | approver.assertApproved("kumquat"); 44 | fail("should have thrown"); 45 | } catch (AssertionFailedError exception) { 46 | assertEquals("banana", exception.getExpected().getValue()); 47 | assertEquals("kumquat", exception.getActual().getValue()); 48 | } 49 | } 50 | 51 | @Ignore("Unignore to see no failure in IDE") 52 | @Test public void what_does_intellij_say() throws IOException { 53 | whenApprovedIs("banana"); 54 | approver.assertApproved("kumquat"); 55 | } 56 | 57 | @Test public void can_assert_with_nothing_approved() throws IOException { 58 | whenApprovedIs(null); 59 | approver.assertSatisfied(); 60 | } 61 | 62 | private void whenApprovedIs(String valueOrNull) throws IOException { 63 | if (valueOrNull == null) 64 | approver.removeApproved(); 65 | else 66 | approver.makeApproved(valueOrNull); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Download](https://maven-badges.herokuapp.com/maven-central/com.oneeyedmen/okeydoke/badge.svg?style=flat-square 3 | )](https://search.maven.org/artifact/com.oneeyedmen/okeydoke) 4 | 5 | okey-doke 6 | ========= 7 | 8 | An Approval Testing library for Java and JUnit - like [Llewellyn Falco's](http://approvaltests.sourceforge.net/) but more Java'y. 9 | 10 | A [helping hand](http://youtu.be/EbqaxWjIgOg) for many testing problems. 11 | 12 | ## Version 2 13 | 14 | If you are upgrading from verions 1.x to version 2.x - JUnit 4 support has been moved from `com.oneeyedmen.junit` to `com.oneeyedmen.junit4`. 15 | 16 | In return, you no longer need to specify whether your are using Java or Kotlin 17 | 18 | ## JUnit 5 19 | 20 | 21 | ```java 22 | public class ApprovalsExtensionTest { 23 | 24 | // Initialise okey-doke. 25 | @RegisterExtension ApprovalsExtension approvals = new ApprovalsExtension(); 26 | // See other constructors to change where the files are stored, 27 | // or change the extension 28 | 29 | @Test 30 | public void something_that_we_want_to_be_the_same_next_time( 31 | Approver approver // approver will be injected 32 | ) { 33 | Object result = doSomeCalculation(42, "banana"); 34 | approver.assertApproved(result); // check that the result is as approved 35 | } 36 | } 37 | ``` 38 | 39 | 40 | The first time you run this test it will fail, but the result of `doSomeCalculation` will be written into a 41 | file next to the test, named `ApprovalsExtensionTest.something_that_we_want_to_be_the_same_next_time.actual` 42 | 43 | You can look at this file to check that it is what you expect, and if it is, approve the test by renaming the file 44 | it to `ApprovalsExtensionTest.something_that_we_want_to_be_the_same_next_time.approved` (or ask the plugin to do it for you). 45 | 46 | From then on the test will pass provided the result of `doSomeCalculation` doesn't change. 47 | If it does change then you can either fix the code if it shouldn't have, or approve the new version. 48 | 49 | ## IntelliJ 50 | 51 | There is an [IntelliJ plugin](https://github.com/s4nchez/okey-doke-idea) (thanks @s4nchez) to help approve your output. 52 | 53 | ## JUnit 4 54 | 55 | We still support JUnit 4 with a Rule - 56 | [ApprovalsRuleTest](src/test/java/com/oneeyedmen/okeydoke/examples/ApprovalsRuleTest.java) 57 | 58 | ```java 59 | @Rule public final ApprovalsRule approver = ApprovalsRule.usualRule(); 60 | 61 | @Test 62 | public void something_that_we_want_to_be_the_same_next_time( 63 | ) { 64 | Object result = doSomeCalculation(42, "banana"); 65 | approver.assertApproved(result); // check that the result is as approved 66 | } 67 | ``` 68 | 69 | Here you can use the `ApprovalsRule` as a approver. 70 | 71 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/sources/FileSystemSourceOfApproval.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.sources; 2 | 3 | import com.oneeyedmen.okeydoke.*; 4 | import com.oneeyedmen.okeydoke.internal.IO; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | public class FileSystemSourceOfApproval implements SourceOfApproval { 10 | 11 | private final Reporter reporter; 12 | private final File approvedDir; 13 | private final File actualDir; 14 | private final String typeExtension; 15 | 16 | public FileSystemSourceOfApproval(File approvedDir, File actualDir, String typeExtension, Reporter reporter) { 17 | this.approvedDir = approvedDir; 18 | this.actualDir = actualDir; 19 | this.typeExtension = typeExtension; 20 | this.reporter = reporter; 21 | } 22 | 23 | public FileSystemSourceOfApproval(File approvedDir, File actualDir, Reporter reporter) { 24 | this(approvedDir, actualDir, "", reporter); 25 | } 26 | 27 | public FileSystemSourceOfApproval(File directory, Reporter reporter) { 28 | this(directory, directory, reporter); 29 | } 30 | 31 | public FileSystemSourceOfApproval withActualDirectory(File actualDirectory) { 32 | return new FileSystemSourceOfApproval(approvedDir, actualDirectory, typeExtension, reporter); 33 | } 34 | 35 | public FileSystemSourceOfApproval withTypeExtension(String typeExtension) { 36 | return new FileSystemSourceOfApproval(approvedDir, actualDir, typeExtension, reporter); 37 | } 38 | 39 | @Override 40 | public Resource actualFor(String testName) { 41 | return new FileResource(actualFileFor(testName)); 42 | } 43 | 44 | @Override 45 | public Resource approvedFor(String testName) { 46 | return new FileResource(approvedFileFor(testName)); 47 | } 48 | 49 | @Override 50 | public void reportFailure(String testName, AssertionError e) { 51 | reporter.reportFailure(actualFileFor(testName), approvedFileFor(testName), e); 52 | } 53 | 54 | @Override 55 | public void checkActualAgainstApproved(String testName, Serializer serializer, Checker checker) throws IOException { 56 | checker.assertEquals( 57 | IO.readResource(approvedFor(testName), serializer), 58 | IO.readResource(actualFor(testName), serializer)); 59 | } 60 | 61 | protected File approvedFileFor(String testName) { return new File(approvedDir, testName + approvedExtension()); } 62 | protected File actualFileFor(String testName) { 63 | return new File(actualDir, testName + actualExtension()); 64 | } 65 | 66 | private String approvedExtension() { 67 | return ".approved" + typeExtension(); 68 | } 69 | 70 | protected String actualExtension() { 71 | return ".actual" + typeExtension(); 72 | } 73 | 74 | private String typeExtension() { 75 | return typeExtension; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/junit4/BaseApprovalsRule.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import com.oneeyedmen.okeydoke.ApproverFactory; 4 | import com.oneeyedmen.okeydoke.BaseApprover; 5 | import com.oneeyedmen.okeydoke.Formatter; 6 | import org.junit.rules.TestWatcher; 7 | import org.junit.runner.Description; 8 | 9 | import java.io.IOException; 10 | import java.io.OutputStream; 11 | import java.io.PrintStream; 12 | 13 | /** 14 | * Use as an @Rule to automate approvals in JUnit. 15 | */ 16 | public class BaseApprovalsRule> extends TestWatcher { 17 | 18 | private final ApproverFactory factory; 19 | private final TestNamer testNamer; 20 | private ApproverT approver; // can only be bound once the test starts 21 | 22 | public BaseApprovalsRule(ApproverFactory factory) { 23 | this(factory, new StandardTestNamer()); 24 | } 25 | 26 | public BaseApprovalsRule(ApproverFactory factory, TestNamer testNamer) { 27 | this.factory = factory; 28 | this.testNamer = testNamer; 29 | } 30 | 31 | public PrintStream printStream() { 32 | return approver().printStream(); 33 | } 34 | 35 | public OutputStream outputStream() throws IOException { 36 | return approver.outputStream(); 37 | } 38 | 39 | public void writeFormatted(ApprovedT o) { 40 | approver().writeFormatted(o); 41 | } 42 | 43 | public void assertApproved(ApprovedT actual) { 44 | approver().assertApproved(actual); 45 | } 46 | 47 | public void assertApproved(T2 actual, Formatter formatter) { 48 | approver().assertApproved(actual, formatter); 49 | } 50 | 51 | public void assertSatisfied() { 52 | if (approver().satisfactionChecked()) 53 | throw new IllegalStateException("I've got too much satisfaction"); 54 | approver().assertSatisfied(); 55 | } 56 | 57 | public void makeApproved(ApprovedT approved) throws IOException { 58 | approver().makeApproved(approved); 59 | } 60 | 61 | @Override 62 | public void starting(Description description) { 63 | approver = createApprover(testNamer.nameFor(description), description.getTestClass()); 64 | } 65 | 66 | @Override 67 | public void succeeded(Description description) { 68 | if (approver().satisfactionChecked()) 69 | return; 70 | assertSatisfied(); 71 | } 72 | 73 | protected ApproverT createApprover(String testName, Class testClass) { 74 | return factory.createApprover(testName, testClass); 75 | } 76 | 77 | public ApproverT approver() { 78 | checkRuleState(); 79 | return approver; 80 | } 81 | 82 | protected void checkRuleState() { 83 | if (approver == null) 84 | throw new IllegalStateException("Something is wrong - check your " + 85 | getClass().getSimpleName() + " is an @Rule field"); 86 | } 87 | 88 | public void removeApproved() throws IOException { 89 | approver().removeApproved(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/ApproverFactories.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import com.oneeyedmen.okeydoke.junit4.BinaryApprovalsRule; 4 | 5 | import java.io.File; 6 | 7 | public class ApproverFactories { 8 | 9 | public static ApproverFactory fileSystemApproverFactory(final File sourceRoot) { 10 | return new ApproverFactory() { 11 | @Override 12 | public Approver createApprover(String testName, Class testClass) { 13 | return new Approver(testName, 14 | Sources.in(sourceRoot, testClass.getPackage()) 15 | ); 16 | } 17 | }; 18 | } 19 | 20 | public static ApproverFactory fileSystemApproverFactory(final File sourceRoot, final String extension) { 21 | return new ApproverFactory() { 22 | @Override 23 | public Approver createApprover(String testName, Class testClass) { 24 | return new Approver(testName, 25 | Sources.in(sourceRoot, testClass.getPackage()).withTypeExtension(extension) 26 | ); 27 | } 28 | }; 29 | } 30 | 31 | public static ApproverFactory streamingApproverFactory(final File sourceRoot) { 32 | return new ApproverFactory() { 33 | @Override 34 | public Approver createApprover(String testName, Class testClass) { 35 | return new Approver(testName, 36 | Sources.streamingInto(sourceRoot, testClass.getPackage()) 37 | ); 38 | } 39 | }; 40 | } 41 | 42 | public static ApproverFactory binaryFileSystemApproverFactory(final File sourceRoot) { 43 | return new ApproverFactory() { 44 | @Override 45 | public BinaryApprover createApprover(String testName, Class testClass) { 46 | return new BinaryApprover(testName, 47 | Sources.in(sourceRoot, testClass.getPackage()) 48 | ); 49 | } 50 | }; 51 | } 52 | 53 | public static BinaryApprovalsRule streamingBinaryApproverFactory(final File sourceRoot) { 54 | return new BinaryApprovalsRule(new ApproverFactory() { 55 | @Override 56 | public BinaryApprover createApprover(String testName, Class testClass) { 57 | return new BinaryApprover(testName, 58 | Sources.streamingInto(sourceRoot, testClass.getPackage()) 59 | ); 60 | } 61 | }); 62 | } 63 | 64 | public static ApproverFactory binaryFileSystemApproverFactory(final File sourceRoot, final String extension) { 65 | return new ApproverFactory() { 66 | @Override 67 | public BinaryApprover createApprover(String testName, Class testClass) { 68 | return new BinaryApprover(testName, 69 | Sources.in(sourceRoot, testClass.getPackage()).withTypeExtension(extension) 70 | ); 71 | } 72 | }; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /okeydoke.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/util/TabulatorTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.util; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Collection; 6 | import java.util.Collections; 7 | 8 | import static java.util.Arrays.asList; 9 | import static org.junit.Assert.assertEquals; 10 | 11 | public class TabulatorTest { 12 | 13 | private final Tabulator tabulator = new Tabulator(); 14 | 15 | @Test public void empty_when_empty_collection() { 16 | Iterable empty = Collections.emptyList(); 17 | assertEquals("", tabulator.tableOf(empty)); 18 | } 19 | 20 | @Test public void empty_when_first_row_empty() { 21 | Iterable emptyRow = Collections.emptyList(); 22 | assertEquals("", tabulator.tableOf(asList(emptyRow))); 23 | } 24 | 25 | @Test public void no_header_row() { 26 | Iterable data = asList( 27 | asList("one", "two", "three"), 28 | asList("four", "five", "siiiiiiix")); 29 | 30 | assertEquals( 31 | "|one |two |three |\n" + 32 | "|four|five|siiiiiiix|\n", 33 | tabulator.tableOf(data)); 34 | } 35 | 36 | @Test public void iterable_of_arrays() { 37 | Iterable data = asList( 38 | new String[] {"one", "two", "three"}, 39 | new String[] {"four", "five", "siiiiiiix"}); 40 | 41 | assertEquals( 42 | "|one |two |three |\n" + 43 | "|four|five|siiiiiiix|\n", 44 | tabulator.tableOf(data)); 45 | } 46 | 47 | @Test public void iterable_of_arrays_of_primitives() { 48 | Iterable data = asList( 49 | new int[] {1, 2, 3}, 50 | new boolean[] {true, false, true}); 51 | 52 | assertEquals( 53 | "|1 |2 |3 |\n" + 54 | "|true|false|true|\n", 55 | tabulator.tableOf(data)); 56 | } 57 | 58 | @Test public void header_row_2() { 59 | Iterable data = asList( 60 | asList("one", "two", "three"), 61 | asList("four", "five", "siiiiiiix")); 62 | 63 | assertEquals( 64 | "|Header 1|Header 2|Header 3 |\n" + 65 | "|--------|--------|---------|\n" + 66 | "|one |two |three |\n" + 67 | "|four |five |siiiiiiix|\n", 68 | tabulator.headedTableOf(data, "Header 1", "Header 2", "Header 3")); 69 | } 70 | 71 | @Test public void header_row_for_no_data() { 72 | Iterable data = Collections.emptyList(); 73 | 74 | assertEquals( 75 | "|Header 1|Header 2|Header 3|\n" + 76 | "|--------|--------|--------|\n", 77 | tabulator.headedTableOf(data, "Header 1", "Header 2", "Header 3")); 78 | } 79 | 80 | @Test public void one_dimension() { 81 | Collection data = asList( 82 | "one", 83 | "two", 84 | "three"); 85 | 86 | assertEquals( 87 | "|one |\n" + 88 | "|two |\n" + 89 | "|three|\n", 90 | tabulator.tableOf(data)); 91 | } 92 | 93 | @Test public void one_dimension_with_header_row() { 94 | Collection data = asList( 95 | "one", 96 | "two", 97 | "three"); 98 | 99 | assertEquals( 100 | "|Header|\n" + 101 | "|------|\n" + 102 | "|one |\n" + 103 | "|two |\n" + 104 | "|three |\n", 105 | tabulator.headedTableOf(data, "Header")); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/com/oneeyedmen/okeydoke/ApproverFileLifecycleTest.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import com.oneeyedmen.okeydoke.sources.FileSystemSourceOfApproval; 4 | import com.oneeyedmen.okeydoke.testutils.CleanDirectoryRule; 5 | import org.junit.Before; 6 | import org.junit.Rule; 7 | import org.junit.Test; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | 12 | import static org.junit.Assert.*; 13 | 14 | public class ApproverFileLifecycleTest { 15 | 16 | @Rule public final CleanDirectoryRule clean = new CleanDirectoryRule(new File("target/approvals")); 17 | 18 | private final FileSystemSourceOfApproval sourceOfApproval = Sources.in(new File("target/approvals")); 19 | private Approver approver; 20 | 21 | @Before public void createApproverInsideCleanDirectoryRule() { 22 | // required because otherwise the directory is cleaned after the approver has created its file inside it 23 | assertFalse(sourceOfApproval.actualFor("testname").exists()); 24 | approver = new Approver("testname", sourceOfApproval); 25 | assertFalse(sourceOfApproval.actualFor("testname").exists()); 26 | } 27 | 28 | @Test public void approved_removes_actual_file() throws IOException { 29 | assertFalse(sourceOfApproval.approvedFor("testname").exists()); 30 | 31 | approver.makeApproved("banana"); 32 | assertEquals("banana".length(), sourceOfApproval.approvedFor("testname").size()); 33 | 34 | approver.assertApproved("banana"); 35 | assertTrue(sourceOfApproval.approvedFor("testname").exists()); 36 | assertFalse(sourceOfApproval.actualFor("testname").exists()); 37 | } 38 | 39 | @Test public void creates_approved_file_when_there_is_none_to_give_a_diff() { 40 | assertFalse(sourceOfApproval.approvedFor("testname").exists()); 41 | 42 | try { 43 | approver.assertApproved("banana"); 44 | } catch (AssertionError expected) {} 45 | assertEquals(0, sourceOfApproval.approvedFor("testname").size()); 46 | assertEquals("banana".length(), sourceOfApproval.actualFor("testname").size()); 47 | } 48 | 49 | @Test public void both_files_present_when_doesnt_match_approved() throws IOException { 50 | assertFalse(sourceOfApproval.approvedFor("testname").exists()); 51 | 52 | approver.makeApproved("banana"); 53 | assertTrue(sourceOfApproval.approvedFor("testname").exists()); 54 | 55 | try { 56 | approver.assertApproved("kumquat"); 57 | } catch (AssertionError expected) {} 58 | assertEquals("banana".length(), sourceOfApproval.approvedFor("testname").size()); 59 | assertEquals("kumquat".length(), sourceOfApproval.actualFor("testname").size()); 60 | } 61 | 62 | @Test public void neither_file_created_if_no_assertion() throws IOException { 63 | assertFalse(sourceOfApproval.actualFor("testname").exists()); 64 | assertFalse(sourceOfApproval.approvedFor("testname").exists()); 65 | 66 | approver.assertSatisfied(); 67 | assertFalse(sourceOfApproval.actualFor("testname").exists()); 68 | assertFalse(sourceOfApproval.approvedFor("testname").exists()); 69 | } 70 | 71 | @Test public void approved_not_removed_if_no_assertion() throws IOException { 72 | approver.makeApproved("banana"); 73 | assertFalse(sourceOfApproval.actualFor("testname").exists()); 74 | assertTrue(sourceOfApproval.approvedFor("testname").exists()); 75 | 76 | try { 77 | approver.assertSatisfied(); 78 | } catch (AssertionError expected) {} 79 | 80 | assertFalse(sourceOfApproval.actualFor("testname").exists()); 81 | assertTrue(sourceOfApproval.approvedFor("testname").exists()); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/BaseApprover.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | import java.io.PrintStream; 6 | 7 | import static com.oneeyedmen.okeydoke.internal.IO.closeQuietly; 8 | 9 | public class BaseApprover { 10 | 11 | private final String testName; 12 | private final SourceOfApproval sourceOfApproval; 13 | private final Formatter formatter; 14 | private final Serializer serializer; 15 | private final Checker checker; 16 | 17 | private Resource actual; 18 | private boolean done; 19 | 20 | protected BaseApprover(String testName, SourceOfApproval sourceOfApproval, 21 | Formatter formatter, 22 | Serializer serializer, 23 | Checker checker) { 24 | this.testName = testName; 25 | this.sourceOfApproval = sourceOfApproval; 26 | this.formatter = formatter; 27 | this.serializer = serializer; 28 | this.checker = checker; 29 | } 30 | 31 | public PrintStream printStream() { 32 | try { 33 | return new PrintStream(actual().outputStream()); 34 | } catch (IOException e) { 35 | throw new RuntimeIOException(e); 36 | } 37 | } 38 | 39 | public OutputStream outputStream() throws IOException { 40 | return actual().outputStream(); 41 | } 42 | 43 | public void writeFormatted(ApprovedT object) { 44 | writeFormatted(object, formatter); 45 | } 46 | 47 | public void writeFormatted(AT object, Formatter aFormatter) { 48 | try { 49 | serializer.writeTo(aFormatter.formatted(object), actual().outputStream()); 50 | } catch (IOException e) { 51 | throw new RuntimeIOException(e); 52 | } 53 | } 54 | 55 | public void assertApproved(ApprovedT actual) { 56 | assertApproved(actual, formatter); 57 | } 58 | 59 | public void assertApproved(AT actual, Formatter aFormatter) { 60 | writeFormatted(actual, aFormatter); 61 | assertSatisfied(); 62 | } 63 | 64 | @SuppressWarnings("FeatureEnvy" /* keeps sourceOfApproval simple */) 65 | public void assertSatisfied() { 66 | try { 67 | actual().outputStream().close(); 68 | sourceOfApproval.checkActualAgainstApproved(testName(), serializer, checker); 69 | actual().remove(); 70 | } catch (AssertionError e) { 71 | sourceOfApproval.reportFailure(testName(), e); 72 | throw e; 73 | } catch (IOException e) { 74 | throw new RuntimeIOException(e); 75 | } finally { 76 | actual = null; 77 | done = true; 78 | } 79 | } 80 | 81 | public void makeApproved(ApprovedT approved) throws IOException { 82 | OutputStream output = sourceOfApproval.approvedFor(testName()).outputStream(); 83 | try { 84 | serializer.writeTo(formatter.formatted(approved), output); 85 | } finally { 86 | closeQuietly(output); 87 | } 88 | } 89 | 90 | public boolean satisfactionChecked() { 91 | return done; 92 | } 93 | 94 | public Formatter formatter() { 95 | return formatter; 96 | } 97 | 98 | private Resource actual() throws IOException { 99 | if (actual == null) 100 | actual = sourceOfApproval.actualFor(testName()); 101 | return actual; 102 | } 103 | 104 | protected String testName() { 105 | return testName; 106 | } 107 | 108 | public void removeApproved() throws IOException { 109 | sourceOfApproval.approvedFor(testName()).remove(); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/util/TestDirectory.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.util; 2 | 3 | import java.io.*; 4 | 5 | /** 6 | * Maintains a directory which is created empty every time. 7 | */ 8 | public class TestDirectory extends File { 9 | 10 | public final static File BASE_DIR; 11 | 12 | static { 13 | String tmpdir = System.getProperty("java.io.tmpdir"); 14 | tmpdir = tmpdir == null ? "C:/temp" : tmpdir; 15 | try { 16 | BASE_DIR = new File(tmpdir).getCanonicalFile(); 17 | // otherwise is short form on Windows 18 | } catch (IOException e) { 19 | throw new RuntimeException(e); 20 | } 21 | } 22 | 23 | public static TestDirectory empty(Object test) { 24 | return new TestDirectory(test); 25 | } 26 | 27 | public TestDirectory(String relativePath) { 28 | super(fileFor(relativePath).toString()); 29 | if (this.isFile()) 30 | throw new RuntimeException(this + " is a file not a directory"); 31 | try { 32 | deleteWithRetry(this, 5); 33 | } catch (IOException e) { 34 | throw new RuntimeException(e); 35 | // don't throw IOException as it's too inconvenient in test initializers 36 | } 37 | mkdirs(); 38 | } 39 | 40 | public TestDirectory(Class testClass) { 41 | this(testClass.getName()); 42 | } 43 | 44 | public TestDirectory(Object test) { 45 | this(test.toString()); 46 | } 47 | 48 | public static void delete(File f) throws IOException { 49 | if (f.isDirectory()) { 50 | for (File c : f.listFiles()) 51 | delete(c); 52 | } 53 | if (!f.delete()) 54 | throw new IOException("Failed to delete file: " + f); 55 | } 56 | 57 | public File createFileFrom(String filename, String contents) throws IOException { 58 | File result = file(filename); 59 | result.getParentFile().mkdirs(); 60 | BufferedWriter w = new BufferedWriter(new FileWriter(result)); 61 | w.write(contents); 62 | w.close(); 63 | return result; 64 | } 65 | 66 | public File touchFile(String filename) throws IOException { 67 | return touchFile(filename, 0); 68 | } 69 | 70 | public File touchFile(String filename, long size) throws IOException { 71 | File result = file(filename); 72 | result.getParentFile().mkdirs(); 73 | RandomAccessFile randomAccessFile = new java.io.RandomAccessFile(result, "rw"); 74 | randomAccessFile.setLength(size); 75 | randomAccessFile.close(); 76 | return result; 77 | } 78 | 79 | public File mkDirs(String path) throws IOException { 80 | File result = file(path); 81 | result.mkdirs(); 82 | if (!result.isDirectory()) 83 | throw new IOException("Could not create dir " + path); 84 | return result; 85 | } 86 | 87 | public boolean hasFile(String filename) { 88 | return file(filename).isFile(); 89 | } 90 | 91 | public File file(String filename) { 92 | return new File(this, filename); 93 | } 94 | 95 | public void remove() throws IOException { 96 | deleteWithRetry(this, 5); 97 | } 98 | 99 | /** 100 | * Some parts of the runtime have a nasty habit of keeping files open a little longer 101 | * than they should. Retry the delete, gc'ing to flush the close on finalize 102 | * out of the woodwork. 103 | */ 104 | private void deleteWithRetry(File file, int retryCount) throws IOException { 105 | if (!file.exists()) 106 | return; 107 | for (int i = 0; i < retryCount - 1 && !file.exists(); i++) { 108 | try { 109 | delete(file); 110 | } catch (IOException ignored) {} 111 | System.gc(); 112 | } 113 | 114 | delete(file); 115 | } 116 | 117 | static File fileFor(String relativePath) { 118 | return new File(BASE_DIR, relativePath); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/junit5/ApprovalsExtension.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit5; 2 | 3 | import com.oneeyedmen.okeydoke.Approver; 4 | import com.oneeyedmen.okeydoke.ApproverFactory; 5 | import com.oneeyedmen.okeydoke.Name; 6 | import com.oneeyedmen.okeydoke.internal.DirectoryFinder; 7 | import org.junit.jupiter.api.extension.*; 8 | 9 | import java.io.File; 10 | import java.lang.reflect.Method; 11 | 12 | import static com.oneeyedmen.okeydoke.ApproverFactories.fileSystemApproverFactory; 13 | import static com.oneeyedmen.okeydoke.internal.DirectoryFinder.findARootDirectory; 14 | 15 | /** 16 | * A JUnit 5 Extension to provide an Approver as a parameter to test functions. 17 | *
18 | * Use as a class in `@ExtendWith(ApprovalsExtension.class)` 19 | *
20 | * or a field 21 | *
22 | * `@RegisterExtension ApprovalsExtension approvals = new ApprovalsExtension();` 23 | */ 24 | public class ApprovalsExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback, ParameterResolver { 25 | 26 | private static final String STORE_KEY = "okeydoke.approver"; 27 | 28 | private final ApproverFactory factory; 29 | private final TestNamer testNamer = new TestNamer(); 30 | 31 | public ApprovalsExtension(ApproverFactory factory) { 32 | this.factory = factory; 33 | } 34 | 35 | public ApprovalsExtension(File sourceRoot) { 36 | this(fileSystemApproverFactory(sourceRoot)); 37 | } 38 | 39 | public ApprovalsExtension(File sourceRoot, String extension) { 40 | this(fileSystemApproverFactory(sourceRoot, extension)); 41 | } 42 | 43 | public ApprovalsExtension(String extension) { 44 | this(fileSystemApproverFactory(findARootDirectory(), extension)); 45 | } 46 | 47 | public ApprovalsExtension() { 48 | this(findARootDirectory()); 49 | } 50 | 51 | @Override 52 | public void beforeTestExecution(ExtensionContext context) { 53 | store(context).put(STORE_KEY, factory.createApprover( 54 | testNamer.nameFor(context.getRequiredTestClass(), context.getRequiredTestMethod()), 55 | context.getRequiredTestClass())); 56 | } 57 | 58 | @Override 59 | public void afterTestExecution(ExtensionContext context) { 60 | if (!context.getExecutionException().isPresent()) { 61 | Approver approver = (Approver) store(context).get(STORE_KEY); 62 | if (!approver.satisfactionChecked()) { 63 | approver.assertSatisfied(); 64 | } 65 | } 66 | } 67 | 68 | @Override 69 | public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { 70 | return Approver.class.equals(parameterContext.getParameter().getType()); 71 | } 72 | 73 | @Override 74 | public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { 75 | if (Approver.class.equals(parameterContext.getParameter().getType())) { 76 | return store(extensionContext).get(STORE_KEY); 77 | } 78 | return null; 79 | } 80 | 81 | private ExtensionContext.Store store(ExtensionContext context) { 82 | return context.getStore(ExtensionContext.Namespace.create(context.getRequiredTestClass().getName(), context.getRequiredTestMethod().getName())); 83 | } 84 | 85 | private static class TestNamer { 86 | public String nameFor(Class testClass, Method testMethod) { 87 | return nameFromClass(testClass) + "." + nameFromMethod(testMethod); 88 | } 89 | 90 | private String nameFromMethod(Method testMethod) { 91 | Name nameAnnotation = testMethod.getAnnotation(Name.class); 92 | if (nameAnnotation != null) { 93 | return nameAnnotation.value(); 94 | } 95 | return testMethod.getName(); 96 | } 97 | 98 | private String nameFromClass(Class testClass) { 99 | Name nameAnnotation = testClass.getAnnotation(Name.class); 100 | if (nameAnnotation != null) { 101 | return nameAnnotation.value(); 102 | } 103 | return testClass.getSimpleName(); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/junit4/TheoryApprovalsRule.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.junit4; 2 | 3 | import com.oneeyedmen.okeydoke.*; 4 | import com.oneeyedmen.okeydoke.formatters.InvocationFormatter; 5 | import com.oneeyedmen.okeydoke.internal.Fred; 6 | import com.oneeyedmen.okeydoke.internal.MethodFinder; 7 | import org.junit.rules.TestWatcher; 8 | import org.junit.runner.Description; 9 | 10 | import java.io.File; 11 | import java.lang.reflect.InvocationHandler; 12 | import java.lang.reflect.InvocationTargetException; 13 | import java.lang.reflect.Method; 14 | import java.util.ArrayList; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | /** 20 | * Use as an @ClassRule to automate approval of @Theories in JUnit 21 | */ 22 | public class TheoryApprovalsRule extends TestWatcher { 23 | 24 | private final MethodFinder methodFinder = new MethodFinder(); 25 | 26 | private final Map approvers = new HashMap(); 27 | private final ApproverFactory factory; 28 | private final TestNamer testNamer; 29 | 30 | private Description description; 31 | 32 | public static TheoryApprovalsRule fileSystemRule(File sourceRoot) { 33 | return new TheoryApprovalsRule(ApproverFactories.fileSystemApproverFactory(sourceRoot)); 34 | } 35 | 36 | public static TheoryApprovalsRule fileSystemRule(String sourceRoot) { 37 | return fileSystemRule(new File(sourceRoot)); 38 | } 39 | 40 | public TheoryApprovalsRule(ApproverFactory factory) { 41 | this(factory, new StandardTestNamer()); 42 | } 43 | 44 | public TheoryApprovalsRule(ApproverFactory factory, TestNamer testNamer) { 45 | this.factory = factory; 46 | this.testNamer = testNamer; 47 | } 48 | 49 | public TheoryApprover approver() { 50 | return new TheoryApprover(); 51 | } 52 | 53 | public void starting(Description description) { 54 | this.description = description; 55 | } 56 | 57 | @Override 58 | protected void succeeded(Description description) { 59 | List errors = new ArrayList(); 60 | for (Approver approver : approvers.values()) { 61 | try { 62 | approver.assertSatisfied(); 63 | } catch (Throwable t) { 64 | errors.add(t); 65 | } 66 | } 67 | if (!errors.isEmpty()) { 68 | rethrow(errors.get(0)); 69 | } 70 | } 71 | 72 | private void rethrow(Throwable t) { 73 | if (t instanceof Error) 74 | throw (Error) t; 75 | else if (t instanceof RuntimeException) 76 | throw (RuntimeException) t; 77 | else throw new RuntimeException(t); 78 | } 79 | 80 | public class TheoryApprover extends TestWatcher { 81 | 82 | private Description theory; 83 | private Formatter invocationFormatter = Formatters.invocationFormatter(); 84 | 85 | public TheoryApprover withInvocationFormatter(InvocationFormatter invocationFormatter) { 86 | this.invocationFormatter = invocationFormatter; 87 | return this; 88 | } 89 | 90 | @Override 91 | public void starting(Description description) { 92 | theory = description; 93 | if (!approvers.containsKey(description)) 94 | approvers.put(theory, factory.createApprover(testNamer.nameFor(description), TheoryApprovalsRule.this.description.getTestClass())); 95 | super.starting(description); 96 | } 97 | 98 | public void lockDownResult(Object result, Object... arguments) { 99 | Invocation invocation = new Invocation(arguments, result); 100 | lockDownResult(invocation); 101 | } 102 | 103 | public void lockDownResult(Invocation invocation) { 104 | Approver approver = approvers.get(theory); 105 | if (approver == null) 106 | throw new IllegalStateException("Something is wrong - check that I am an @Rule!"); 107 | approver.transcript().appendFormatted(invocation, invocationFormatter).endl(); 108 | } 109 | 110 | public void lockDownReflectively(Object object, String methodName, Object... arguments) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { 111 | List methods = methodFinder.findMethods(methodFinder.classFor(object), methodName, arguments); 112 | for (Method method : methods) { 113 | try { 114 | lockDownResult(new Invocation(object, method, arguments)); 115 | return; 116 | } catch (IllegalArgumentException wrongArguments) { 117 | // ho hum, try the next 118 | } 119 | } 120 | throw new NoSuchMethodException(methodName); 121 | } 122 | 123 | // experimental 124 | public T lockDown(final T object) { 125 | InvocationHandler handler = new InvocationHandler() { 126 | @Override 127 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 128 | Object result = method.invoke(object, args); 129 | lockDownResult(result, args); 130 | return result; 131 | } 132 | }; 133 | return (T) Fred.newProxyInstance(object.getClass(), handler); 134 | } 135 | 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/oneeyedmen/okeydoke/util/Tabulator.java: -------------------------------------------------------------------------------- 1 | package com.oneeyedmen.okeydoke.util; 2 | 3 | import com.oneeyedmen.okeydoke.internal.LyingWrappingIterator; 4 | 5 | import java.lang.reflect.Array; 6 | import java.util.*; 7 | 8 | public class Tabulator { 9 | 10 | public String tableOf(Iterable data) { 11 | return tableOf(data, columnSizes(data)); 12 | } 13 | 14 | public String headedTableOf(Iterable data, String... headers) { 15 | Iterable dataWithHeader = withHeader(data, headers); 16 | int[] columnSizes = columnSizes(dataWithHeader); 17 | return tableOf(withDivider(dataWithHeader, columnSizes), columnSizes); 18 | } 19 | 20 | private int[] columnSizes(Iterable data) { 21 | int[] result = {}; 22 | for (Object row : data) { 23 | Collection rowCollection = collectionOf(row); 24 | if (result.length == 0) 25 | result = new int[rowCollection.size()]; 26 | int c = 0; 27 | for (Object cell : rowCollection) { 28 | int cellLength = String.valueOf(cell).length(); 29 | result[c] = Math.max(result[c], cellLength); 30 | ++c; 31 | } 32 | } 33 | return result; 34 | } 35 | 36 | private Collection collectionOf(Object row) { 37 | if (row instanceof Collection) 38 | return (Collection) row; 39 | if (row.getClass().isArray()) 40 | return asList(row); 41 | return Collections.singleton(row); 42 | } 43 | 44 | private String tableOf(Iterable data, int[] columnSizes) { 45 | if (columnSizes.length == 0) 46 | return ""; 47 | List> formatted = formattedCells(data, columnSizes); 48 | StringBuffer result = new StringBuffer(); 49 | for (List row: formatted) { 50 | formatRowInto(result, row); 51 | } 52 | return result.toString(); 53 | } 54 | 55 | private void formatRowInto(StringBuffer result, List row) { 56 | result.append('|'); 57 | for (String cell : row) { 58 | result.append(cell).append('|'); 59 | } 60 | result.append('\n'); 61 | } 62 | 63 | private List> formattedCells(Iterable data, int[] columnSizes) { 64 | List> result = new ArrayList>(); 65 | for (Object row : data) { 66 | result.add(formattedRow(collectionOf(row), columnSizes)); 67 | } 68 | return result; 69 | } 70 | 71 | private List formattedRow(Iterable rowCollection, int[] columnSizes) { 72 | List resultRow = new ArrayList(); 73 | int c = 0; 74 | for (Object cell : rowCollection) { 75 | resultRow.add(formatCell(String.valueOf(cell), columnSizes[c])); 76 | ++c; 77 | } 78 | return resultRow; 79 | } 80 | 81 | private String formatCell(String s, int columnSize) { 82 | if (s.length() == columnSize) 83 | return s; 84 | StringBuilder result = new StringBuilder(columnSize); 85 | result.append(s); 86 | while (result.length() < columnSize) { 87 | result.append(' '); 88 | } 89 | return result.toString(); 90 | } 91 | 92 | @SuppressWarnings("unchecked") 93 | public static List asList(final Object array) { 94 | if (!array.getClass().isArray()) 95 | throw new IllegalArgumentException("Not an array"); 96 | return new AbstractList() { 97 | @Override 98 | public T get(int index) { 99 | return (T) Array.get(array, index); 100 | } 101 | 102 | @Override 103 | public int size() { 104 | return Array.getLength(array); 105 | } 106 | }; 107 | } 108 | 109 | @SuppressWarnings("unchecked") 110 | private Iterable withHeader(final Iterable data, final String... headers) { 111 | return new Iterable() { 112 | @Override public Iterator iterator() { 113 | return new LyingWrappingIterator(data.iterator()) { 114 | @Override protected boolean hasNext(int i) { 115 | return wrapped.hasNext() || i < 1; 116 | } 117 | 118 | @Override protected Object next(int i) { 119 | return i == 0 ? headers : wrapped.next(); 120 | } 121 | }; 122 | } 123 | }; 124 | 125 | } 126 | 127 | @SuppressWarnings("unchecked") 128 | private Iterable withDivider(final Iterable data, final int[] columnSizes) { 129 | return new Iterable() { 130 | @Override public Iterator iterator() { 131 | return new LyingWrappingIterator(data.iterator()) { 132 | @Override protected boolean hasNext(int i) { 133 | return wrapped.hasNext() || i < 2; 134 | } 135 | 136 | @Override protected Object next(int i) { 137 | return i == 1 ? dividerData(columnSizes) : wrapped.next(); 138 | } 139 | }; 140 | } 141 | }; 142 | } 143 | 144 | private Collection dividerData(int[] columnSizes) { 145 | List result = new ArrayList(columnSizes.length); 146 | for (int i = 0; i < columnSizes.length; i++) { 147 | result.add(times('-', columnSizes[i])); 148 | } 149 | return result; 150 | } 151 | 152 | private String times(char ch, int repeat) { 153 | StringBuffer result = new StringBuffer(repeat); 154 | for (int i = 0; i < repeat; i++) { 155 | result.append(ch); 156 | } 157 | return result.toString(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | com.oneeyedmen 5 | okeydoke 6 | 2.0.4-SNAPSHOT 7 | jar 8 | 9 | okeydoke 10 | An approvals test library 11 | https://github.com/dmcg/okey-doke 12 | 13 | 14 | Apache License, Version 2.0 15 | http://www.apache.org/licenses/LICENSE-2.0.txt 16 | repo 17 | 18 | 19 | 20 | 21 | dmcg 22 | Duncan McGregor 23 | 24 | 25 | 26 | 27 | UTF-8 28 | UTF-8 29 | 30 | 31 | 32 | scm:git:git@github.com:dmcg/okey-doke.git 33 | scm:git:git@github.com:dmcg/okey-doke.git 34 | https://github.com/dmcg/okey-doke 35 | HEAD 36 | 37 | 38 | 39 | 40 | org.opentest4j 41 | opentest4j 42 | 1.2.0 43 | 44 | 45 | 46 | org.junit.jupiter 47 | junit-jupiter 48 | 5.6.0 49 | test 50 | 51 | 52 | 54 | 55 | junit 56 | junit 57 | 4.13.1 58 | provided 59 | 60 | 61 | org.jmock 62 | jmock-junit4 63 | 2.6.0 64 | provided 65 | 66 | 67 | org.jmock 68 | jmock-legacy 69 | 2.6.0 70 | provided 71 | 72 | 73 | org.junit.jupiter 74 | junit-jupiter-api 75 | 5.6.0 76 | provided 77 | 78 | 79 | 80 | 81 | 82 | 83 | true 84 | org.apache.maven.plugins 85 | maven-compiler-plugin 86 | 3.7.0 87 | 88 | 1.8 89 | 1.8 90 | true 91 | true 92 | 93 | 94 | 95 | org.apache.maven.plugins 96 | maven-source-plugin 97 | 3.0.1 98 | 99 | 100 | attach-sources 101 | 102 | jar 103 | 104 | 105 | 106 | 107 | 108 | org.apache.maven.plugins 109 | maven-javadoc-plugin 110 | 2.10.4 111 | 112 | 113 | attach-javadocs 114 | 115 | jar 116 | 117 | 118 | 119 | 120 | 121 | org.apache.maven.plugins 122 | maven-gpg-plugin 123 | 1.6 124 | 125 | 126 | sign-artifacts 127 | verify 128 | 129 | sign 130 | 131 | 132 | 133 | --pinentry-mode 134 | loopback 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | org.sonatype.plugins 143 | nexus-staging-maven-plugin 144 | 1.6.8 145 | true 146 | 147 | ossrh 148 | https://oss.sonatype.org/ 149 | true 150 | 151 | 152 | 153 | org.apache.maven.plugins 154 | maven-release-plugin 155 | 2.5.3 156 | 157 | 176 | 177 | 178 | 179 | 180 | 181 | ossrh 182 | https://oss.sonatype.org/content/repositories/snapshots 183 | 184 | 185 | ossrh 186 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 187 | 188 | 189 | 190 | --------------------------------------------------------------------------------