diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..c376259
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,35 @@
+name: Testing DealNews\GetConfig
+
+on: [push]
+
+jobs:
+ test:
+
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ php-versions: ['8.0', '8.1', '8.2']
+ include:
+ - operating-system: 'ubuntu-latest'
+ php-versions: '8.0'
+ phpunit-versions: 9
+ steps:
+
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Composer Install
+ uses: php-actions/composer@v6
+ with:
+ php_version: ${{ matrix.php-versions }}
+
+ - name: PHPUnit tests
+ uses: php-actions/phpunit@v3
+ with:
+ php_extensions: "pcov yaml"
+ version: "9.6"
+ php_version: ${{ matrix.php-versions }}
+
+ - name: Run Phan
+ uses: k1LoW/phan-action@v0
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7594d84
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,21 @@
+# Temporary files
+.DS_Store
+*~
+*.sw[aop]
+.php-cs-fixer.cache
+.phpunit.result.cache
+
+# Deployment dependencies
+vendor
+composer.lock
+phpunit.xml
+
+# IDE project settings
+.project
+.settings
+.idea
+*.sublime-workspace
+*.sublime-project
+
+# test artifacts
+tests/fixtures/test_copy.db
diff --git a/.phan/config.php b/.phan/config.php
new file mode 100644
index 0000000..0349851
--- /dev/null
+++ b/.phan/config.php
@@ -0,0 +1,116 @@
+ true,
+
+ // Allow null to be cast as any type and for any
+ // type to be cast to null.
+ "null_casts_as_any_type" => true,
+
+ // If this has entries, scalars (int, float, bool, string, null)
+ // are allowed to perform the casts listed.
+ // E.g. ['int' => ['float', 'string'], 'float' => ['int'], 'string' => ['int'], 'null' => ['string']]
+ // allows casting null to a string, but not vice versa.
+ // (subset of scalar_implicit_cast)
+ 'scalar_implicit_partial' => [
+ 'int' => ['float', 'string'],
+ 'float' => ['int'],
+ 'string' => ['int'],
+ 'null' => ['string', 'bool'],
+ 'bool' => ['null'],
+ ],
+
+ // Backwards Compatibility Checking
+ 'backward_compatibility_checks' => false,
+
+ // Run a quick version of checks that takes less
+ // time
+ "quick_mode" => true,
+
+ // Only emit critical issues
+ "minimum_severity" => 0,
+
+ // A set of fully qualified class-names for which
+ // a call to parent::__construct() is required
+ 'parent_constructor_required' => [
+ ],
+
+ // Add any issue types (such as 'PhanUndeclaredMethod')
+ // here to inhibit them from being reported
+ 'suppress_issue_types' => [
+ // These report false positives in libraries due
+ // to them not being used by any of the other
+ // library code.
+ 'PhanUnreferencedPublicClassConstant',
+ 'PhanWriteOnlyProtectedProperty',
+ 'PhanUnreferencedPublicMethod',
+ 'PhanUnreferencedUseNormal',
+ 'PhanUnreferencedProtectedMethod',
+ 'PhanUnreferencedProtectedProperty',
+
+ ],
+
+ // A list of directories that should be parsed for class and
+ // method information. After excluding the directories
+ // defined in exclude_analysis_directory_list, the remaining
+ // files will be statically analyzed for errors.
+ //
+ // Thus, both first-party and third-party code being used by
+ // your application should be included in this list.
+ 'directory_list' => [
+ 'src',
+ 'vendor',
+ 'tests',
+ ],
+
+ // A list of directories holding code that we want
+ // to parse, but not analyze
+ "exclude_analysis_directory_list" => [
+ "vendor",
+ "tests",
+ ],
+
+ // A file list that defines files that will be excluded
+ // from parsing and analysis and will not be read at all.
+ //
+ // This is useful for excluding hopelessly unanalyzable
+ // files that can't be removed for whatever reason.
+ 'exclude_file_list' => [
+ ],
+
+ // Set to true in order to attempt to detect dead
+ // (unreferenced) code. Keep in mind that the
+ // results will only be a guess given that classes,
+ // properties, constants and methods can be referenced
+ // as variables (like `$class->$property` or
+ // `$class->$method()`) in ways that we're unable
+ // to make sense of.
+ 'dead_code_detection' => true,
+];
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..33de634
--- /dev/null
+++ b/.php-cs-fixer.dist.php
@@ -0,0 +1,68 @@
+in(__DIR__)
+;
+
+return (new PhpCsFixer\Config())
+ ->setRules([
+ '@PSR2' => true,
+ 'array_syntax' => [
+ 'syntax' => 'short',
+ ],
+ 'binary_operator_spaces' => [
+ 'default' => 'align_single_space',
+ ],
+ 'blank_line_after_opening_tag' => true,
+ 'blank_line_before_statement' => ['statements' => ['return']],
+ 'braces_position' => [
+ 'allow_single_line_anonymous_functions' => true,
+ 'allow_single_line_empty_anonymous_classes' => true,
+ 'anonymous_classes_opening_brace' => 'same_line',
+ 'anonymous_functions_opening_brace' => 'same_line',
+ 'classes_opening_brace' => 'same_line',
+ 'control_structures_opening_brace' => 'same_line',
+ 'functions_opening_brace' => 'same_line',
+ ],
+ 'combine_consecutive_unsets' => true,
+ 'concat_space' => [
+ 'spacing' => 'one',
+ ],
+ 'declare_equal_normalize' => true,
+ 'escape_implicit_backslashes' => [
+ 'single_quoted' => true,
+ 'double_quoted' => true,
+ ],
+ 'function_typehint_space' => true,
+ 'include' => true,
+ 'lowercase_cast' => true,
+// 'class_attributes_separation' => ['elements' => ['method']],
+ 'native_function_casing' => true,
+ 'no_blank_lines_after_phpdoc' => true,
+ 'no_empty_comment' => true,
+ 'no_empty_statement' => true,
+ 'no_mixed_echo_print' => [
+ 'use' => 'echo',
+ ],
+ 'no_multiline_whitespace_around_double_arrow' => true,
+ 'multiline_whitespace_before_semicolons' => false,
+ 'no_short_bool_cast' => true,
+ 'no_singleline_whitespace_before_semicolons' => true,
+ 'no_spaces_around_offset' => true,
+ 'no_unused_imports' => true,
+ 'no_whitespace_before_comma_in_array' => true,
+ 'no_whitespace_in_blank_line' => true,
+ 'object_operator_without_whitespace' => true,
+ 'ordered_imports' => true,
+ 'short_scalar_cast' => true,
+ 'single_blank_line_before_namespace' => true,
+ 'single_quote' => true,
+ 'space_after_semicolon' => true,
+ 'ternary_operator_spaces' => true,
+ 'trailing_comma_in_multiline' => ['elements' => ['arrays']],
+ 'trim_array_spaces' => true,
+ 'unary_operator_spaces' => true,
+ 'whitespace_after_comma_in_array' => true,
+ ])
+ ->setFinder($finder)
+;
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e382825
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# Test Helpers
+
+A collection of traits that are useful when writing PHPUnit tests.
\ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..c18c5ec
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,45 @@
+{
+ "name": "dealnews/test-helpers",
+ "type": "library",
+ "license": "BSD-3-Clause",
+ "description": "A PHP library of traits for use in PHPUnit test cases.",
+ "config": {
+ "optimize-autoloader": true,
+ "discard-changes": true,
+ "sort-packages": true
+ },
+ "require": {
+ "php": "^8.0",
+ "guzzlehttp/guzzle": "^7.8",
+ "phpunit/phpunit": "^9.6"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.38",
+ "php-parallel-lint/php-parallel-lint": "^1.3"
+ },
+ "autoload": {
+ "psr-4" : {
+ "DealNews\\TestHelpers\\" : "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "DealNews\\TestHelpers\\Tests\\": "tests/"
+ }
+ },
+ "scripts": {
+ "phan": [
+ "docker run --rm -e PHAN_DISABLE_XDEBUG_WARN=1 -v `pwd`:/mnt/src -w /mnt/src phanphp/phan:5 -p"
+ ],
+ "test": [
+ "parallel-lint src/ tests/",
+ "phpunit --colors=never"
+ ],
+ "lint": [
+ "parallel-lint src/ tests/"
+ ],
+ "fix": [
+ "php-cs-fixer fix --config .php-cs-fixer.dist.php src tests"
+ ]
+ }
+}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..984f514
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,31 @@
+
+
+
+
+ ./src
+
+
+ ./tests
+ ./vendor
+
+
+
+
+
+
+
+ ./tests
+
+
+
+
+ functional
+
+
+
+
+
+
+
+
+
diff --git a/src/AssertionStack.php b/src/AssertionStack.php
new file mode 100644
index 0000000..3e484ae
--- /dev/null
+++ b/src/AssertionStack.php
@@ -0,0 +1,100 @@
+
+ * @copyright 1997-Present DealNews.com, Inc
+ * @package \DealNews\TestHelpers
+ */
+trait AssertionStack {
+
+ /**
+ * @var \PHPUnit\Framework\TestCase
+ */
+ protected static $testcase;
+
+ /**
+ * Stack of assertEqual calls for a method to check the method's parameters that were passed in
+ *
+ * @var array
+ */
+ protected static $assert_equal_stacks = [];
+
+ /**
+ * Sets the currently used testcase object so that this mock class can reference and use
+ * the assertEquals method
+ *
+ * @param \PHPUnit\Framework\TestCase $testCase
+ */
+ public function setTestCaseForMock(\PHPUnit\Framework\TestCase $testCase) : void {
+ self::$testcase = $testCase;
+ }
+
+ /**
+ * Resets the assert equal stack
+ */
+ public function resetAssertEqualStack() {
+ self::$assert_equal_stacks = [];
+ }
+
+ /**
+ * Adds multiple return values for a method
+ *
+ * @param string $func The method name
+ * @param array $expected_param_values A list of arrays. Each array contains a list of param values that are expected
+ */
+ public function setAssertEqualStack(string $func, array $expected_param_values) : void {
+ foreach ($expected_param_values as $expected_param_value_array) {
+ $this->addAssertEqualStack($func, $expected_param_value_array);
+ }
+ }
+
+ /**
+ * Adds a single set of expected parameters to be checked with assertEqual
+ *
+ * @param string $func The function
+ * @param array $expected_param_values An array of expected param values
+ */
+ public function addAssertEqualStack(string $func, array $expected_param_values) : void {
+ self::$assert_equal_stacks[$func][] = $expected_param_values;
+ }
+
+ /**
+ * Takes one set of expected param values off of the top of the stack for a particular func/method
+ * and calls assertEqual for each parameter
+ *
+ * @param string $func The function
+ * @param array $params The parameter values that were passed when the method was called.
+ *
+ */
+ protected function callAssertEqualFromStack(string $func, array $params) : void {
+ $classname = get_parent_class($this);
+
+ if (empty(self::$testcase)) {
+ trigger_error('The testcase object was never set. See: setTestCaseForMock() method that should exist in the mock class of ' . $classname, E_USER_ERROR);
+ }
+
+ if (!empty(self::$assert_equal_stacks[$func])) {
+ $expected_values = array_shift(self::$assert_equal_stacks[$func]);
+
+ self::$testcase->assertEquals(count($expected_values), count($params), 'The number of expected parameters and passed-in parameters does not match for the method that is a mock of ' . $classname . '::' . $func . '()');
+
+ $reflect = new \ReflectionMethod($this, $func);
+ $param_definitions = $reflect->getParameters();
+
+ foreach ($params as $parameter_number => $parameter_value) {
+ $parameter_name = '[Unknown parameter name]';
+ if (!empty($param_definitions[$parameter_number])) {
+ $parameter_name = $param_definitions[$parameter_number]->getName();
+ }
+
+ self::$testcase->assertEquals(array_shift($expected_values), $parameter_value, $parameter_name . ' parameter does not have the expected value for the method that is a mock of ' . $classname . '::' . $func . '()');
+ }
+ }
+ }
+}
diff --git a/src/CatchErrors.php b/src/CatchErrors.php
new file mode 100644
index 0000000..465842f
--- /dev/null
+++ b/src/CatchErrors.php
@@ -0,0 +1,28 @@
+
+ * @copyright 1997-Present DealNews.com, Inc
+ * @package DealNews\TestHelpers
+ */
+trait CatchErrors {
+
+ public function setUp(): void {
+ parent::setUp();
+ set_error_handler(
+ static function ($errno, $errstr) {
+ throw new \Exception($errstr, $errno);
+ },
+ E_ALL
+ );
+ }
+
+ public function tearDown(): void {
+ parent::tearDown();
+ restore_error_handler();
+ }
+}
diff --git a/src/Fixtures.php b/src/Fixtures.php
new file mode 100644
index 0000000..b4118e0
--- /dev/null
+++ b/src/Fixtures.php
@@ -0,0 +1,163 @@
+
+ * @author Jeremy Earle
+ * @copyright 1997-Present DealNews.com, Inc
+ * @package \DealNews\TestHelpers
+ */
+trait Fixtures {
+
+ /**
+ * Directory where test are stored
+ */
+ public static $test_directory;
+
+ /**
+ * Directory where fixtures are stored
+ */
+ public static $fixture_directory;
+
+ /**
+ * Set test dir and fixture dir
+ */
+ public static function setUpBeforeClass(): void {
+ if (!defined('TEST_DIR')) {
+ define('TEST_DIR', null);
+ }
+
+ if (!defined('FIXTURE_DIR')) {
+ define('FIXTURE_DIR', null);
+ }
+
+ self::$test_directory = realpath(self::$test_directory ?? TEST_DIR ?? __DIR__ . '/../../../../tests');
+ self::$fixture_directory = realpath(self::$fixture_directory ?? FIXTURE_DIR ?? self::$test_directory . '/fixtures');
+
+ if (empty(self::$test_directory)) {
+ throw new \RuntimeException('Unable to find test directory.');
+ }
+ }
+
+ /**
+ * Wrapper for assertSame that sorts array keys before comparing the
+ * arrays. If the values are not arrays, there is no difference with
+ * this method and assertSame
+ *
+ * @param mixed $expected The expected value
+ * @param mixed $actual The actual value
+ * @param string $message The message
+ */
+ public function assertSameData(mixed $expected, mixed $actual, string $message = ''): void {
+ if (is_array($expected) && is_array($actual)) {
+ $expected = $this->sortKeysRecursive($expected);
+ $actual = $this->sortKeysRecursive($actual);
+ }
+
+ $this->assertSame($expected, $actual, $message);
+ }
+
+ /**
+ * Returns the path to a fixture file
+ *
+ * @param string $fixture The fixture name
+ *
+ * @return string The fixture filename.
+ */
+ public function getFixtureFile(string $fixture): string {
+
+ // data providers are loaded before setUpBeforeClass is run
+ // so make sure it has been called before using the variables
+ if (self::$fixture_directory === null) {
+ self::setUpBeforeClass();
+ }
+
+ $file = realpath(self::$fixture_directory . "/$fixture");
+ $this->assertTrue(!empty($file) && file_exists($file), "Fixture $fixture does not exist");
+
+ return $file;
+ }
+
+ /**
+ * Returns the contents of a fixture file
+ *
+ * @param string $fixture The fixture name
+ *
+ * @return string The fixture data.
+ */
+ public function getFixtureData(string $fixture): string {
+ return file_get_contents($this->getFixtureFile($fixture));
+ }
+
+ /**
+ * Returns fixture data JSON decoded
+ *
+ * @param string $fixture The fixture name
+ *
+ * @return array|object The fixture data.
+ */
+ public function getFixtureJson(string $fixture, bool $as_array = true) {
+ return json_decode($this->getFixtureData($fixture), $as_array);
+ }
+
+ /**
+ * Returns fixture data as an array of JSON decoded lines
+ *
+ * @param string $fixture The fixture name
+ *
+ * @return array The fixture data.
+ */
+ public function getFixtureJsonLines(string $fixture, bool $as_array = true): array {
+ $data = [];
+
+ $fp = fopen($this->getFixtureFile($fixture), 'r');
+
+ while (!feof($fp)) {
+ $line = trim(fgets($fp));
+ if (!empty($line)) {
+ $record = json_decode($line, $as_array);
+ if (is_array($record) || is_object($record)) {
+ $data[] = $record;
+ }
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Checks if a fixture string is a fixture file
+ *
+ * @param string $fixture The fixture
+ *
+ * @return bool True if the specified fixture is fixture file, False otherwise.
+ */
+ protected function isFixtureFile(string $fixture): bool {
+ if (!empty($fixture)) {
+ $file = realpath(self::$fixture_directory . "/$fixture");
+ }
+
+ return !empty($file) && file_exists($file);
+ }
+
+ /**
+ * Sorts arrays by key recursively
+ *
+ * @param array $arr The arr
+ *
+ * @return array
+ */
+ protected function sortKeysRecursive(array $arr): array {
+ ksort($arr);
+ foreach ($arr as $k => $v) {
+ if (is_array($v)) {
+ $arr[$k] = $this->sortKeysRecursive($v);
+ }
+ }
+
+ return $arr;
+ }
+}
diff --git a/src/Guzzle.php b/src/Guzzle.php
new file mode 100644
index 0000000..653ca03
--- /dev/null
+++ b/src/Guzzle.php
@@ -0,0 +1,58 @@
+
+ * @author Jeremy Earle
+ * @copyright 1997-Present DealNews.com, Inc
+ * @package \DealNews\TestHelpers
+ */
+trait Guzzle {
+
+ use Fixtures;
+
+ /**
+ * Create a guzzle mock.
+ *
+ * @param integer|array $codes
+ * @param array $fixtures
+ * @param array $container
+ *
+ * @return GuzzleClient
+ */
+ public function makeGuzzleMock($codes, array $fixtures, array &$container): GuzzleClient {
+ $responses = [];
+
+ if (is_array($codes)) {
+ $this->assertEquals(count($codes), count($fixtures), 'When using an array of codes, the number of codes must match the number of fixtures');
+ }
+
+ foreach ($fixtures as $fixture) {
+ if (is_array($fixture)) {
+ $data = json_encode($fixture);
+ } elseif (is_string($fixture) && $this->isFixtureFile($fixture)) {
+ $data = $this->getFixtureData($fixture);
+ } else {
+ $data = $fixture;
+ }
+ $code = is_array($codes) ? array_shift($codes) : $codes;
+ $responses[] = new Response($code, ['Content-Type' => 'application/json'], $data);
+ }
+
+ $history = Middleware::history($container);
+ $mock = new MockHandler($responses);
+ $handler_stack = HandlerStack::create($mock);
+ $handler_stack->push($history);
+
+ return new GuzzleClient(['handler' => $handler_stack]);
+ }
+}
diff --git a/src/Methods.php b/src/Methods.php
new file mode 100644
index 0000000..401440d
--- /dev/null
+++ b/src/Methods.php
@@ -0,0 +1,238 @@
+
+ * @author Jeremy Earle
+ * @copyright 1997-Present DealNews.com, Inc
+ * @package \DealNews\TestHelpers
+ */
+trait Methods {
+ public array $method_counts = [];
+ public static array $static_method_counts = [];
+
+ protected array $stacks = [];
+ protected static array $static_stacks = [];
+
+ protected array $return_values = [];
+ protected static array $static_return_values = [];
+
+ /**
+ * Resets the stacks and method counts, then adds a stack of return values for each method mentioned
+ * in the provided array
+ *
+ * @param array $stacks
+ */
+ public function _addMultipleMethodResponses(array $stacks) {
+ $this->stacks = [];
+ $this->method_counts = [];
+ foreach ($stacks as $func => $stack) {
+ $this->_addMultipleResponses($func, $stack);
+ }
+ }
+
+ /**
+ * Sets the stack/list of return values for the provided method
+ *
+ * @param string $func The name of the method
+ * @param array $return A list/stack of return values you want the method to return
+ */
+ public function _addMultipleResponses(string $func, array $return) {
+ self::_checkMethodExists($func);
+ $this->stacks[$func] = $return;
+ }
+
+ /**
+ * Adds a specific value that should be returned when the provided param values are given to
+ * the provided method name.
+ *
+ * This is not a stack of return values like _addMultipleResponses or _addMultipleMethodResponses uses. And once the
+ * method is called with the expected parameters, it will return your provided return value for that method call and
+ * each subsequent call with those parameter values.
+ *
+ * @param string $func The name of the method
+ * @param array $params A list of the parameter values that are expected that should yield this return value
+ * @param mixed $return The return value
+ */
+ public function _addReturnValueWithParams(string $func, array $params, $return) {
+ self::_checkMethodExists($func);
+ $this->return_values[$func][serialize($params)] = $return;
+ }
+
+ /**
+ * Resets the stacks, counts, etc... associated with static method calls. Then adds a stack of return values for each
+ * static method mentioned in the provided array
+ *
+ * @param array $stacks
+ */
+ public static function _addMultipleStaticMethodResponses(array $stacks) {
+ self::_resetStaticResponses();
+ foreach ($stacks as $func => $stack) {
+ self::_addMultipleStaticResponses($func, $stack);
+ }
+ }
+
+ /**
+ * Sets the stack/list of return values for the provided static method
+ *
+ * @param string $func The name of the method
+ * @param array $return A list/stack of return values you want the method to return
+ */
+ public static function _addMultipleStaticResponses(string $func, array $return) {
+ self::_checkMethodExists($func);
+ self::$static_stacks[$func] = $return;
+ }
+
+ /**
+ * Resets static properties associated with static method stacks, counts, etc...
+ *
+ * Should be called when calling static methods at the top of each test.
+ */
+ public static function _resetStaticResponses() {
+ self::$static_stacks = [];
+ self::$static_method_counts = [];
+ self::$static_return_values = [];
+ }
+
+ /**
+ * Adds a specific value that should be returned when the provided param values are given to
+ * the provided static method name.
+ *
+ * This is not a stack of return values like _addMultipleStaticResponses or _addMultipleStaticMethodResponses uses. And once the
+ * static method is called with the expected parameters, it will return your provided return value for that method call and
+ * each subsequent call with those parameter values.
+ *
+ * @param string $func The name of the method
+ * @param array $params A list of the parameter values that are expected that should yield this return value
+ * @param mixed $return The return value
+ */
+ public static function _addStaticReturnValueWithParams(string $func, array $params, $return) {
+ self::_checkMethodExists($func);
+ self::$static_return_values[$func][serialize($params)] = $return;
+ }
+
+ /**
+ * Checks to make sure that the provided method name exists in the parent class. This assumes that your mock class extends
+ * a class (the parent). If not, the class itself will be checked that it contains the method.
+ *
+ * @param string $func The method name
+ */
+ protected static function _checkMethodExists(string $func) {
+ $called_class = get_called_class();
+ $parent = get_parent_class($called_class);
+
+ // Sometimes a mock class will implement and interface instead
+ // of extending a class. In that case,
+ if (empty($parent)) {
+ $interfaces = class_implements($called_class);
+ if (empty($interfaces)) {
+ throw new \LogicException("$called_class must extend another class or implement an interface which defines $func to add a mock response with the Mock\\Methods trait.");
+ }
+ foreach ($interfaces as $interface) {
+ if (method_exists($interface, $func)) {
+ $parent = $interface;
+ break;
+ }
+ }
+ }
+
+ if (empty($parent) || !method_exists($parent, $func)) {
+ if (empty($parent)) {
+ $parent = $called_class;
+ }
+ throw new \InvalidArgumentException("Method $func not found for $parent when attempting to add a mock response with the Mock\\Methods trait.");
+ }
+ }
+
+ /**
+ * Pulls the next static method response/return value off of the stack and returns it for the provided static method
+ * name. It also increments the static method count.
+ *
+ * @param string $func The name of the method
+ * @param mixed $default A default value to use if there are no return values on the stack
+ *
+ * @return mixed|null
+ */
+ protected static function _getNextStaticResponse(string $func, $default) {
+ self::_incrementCount($func, self::$static_method_counts);
+
+ return empty(self::$static_stacks[$func]) ? $default : array_shift(self::$static_stacks[$func]);
+ }
+
+ /**
+ * Pulls the next method response/return value off of the stack and returns it for the provided method name. It also
+ * increments the method count.
+ *
+ * @param string $func The name of the method
+ * @param mixed $default A default value to use of there are no return values on the stack
+ *
+ * @return mixed|null
+ */
+ protected function _getNextResponse(string $func, $default) {
+ self::_incrementCount($func, $this->method_counts);
+
+ return empty($this->stacks[$func]) ? $default : array_shift($this->stacks[$func]);
+ }
+
+ /**
+ * Checks to see if there is a return value set that matched the provided parameter values. If there is, it returns it.
+ * Otherwise, it returns the default value provided.
+ *
+ * Also, increments the method counts.
+ *
+ * @param string $func The name of the method
+ * @param array $params A list of parameter values that were passed to the method name provided
+ * @param mixed|null $default A default value to use if there is no matched return value for this set of parameter values
+ *
+ * @return mixed|null
+ */
+ protected function _getResponseWithParams(string $func, array $params, $default = null) {
+ self::_incrementCount($func, $this->method_counts);
+ $ser = serialize($params);
+
+ return (
+ array_key_exists($func, $this->return_values) &&
+ array_key_exists($ser, $this->return_values[$func])
+ ) ? $this->return_values[$func][$ser] : $default;
+ }
+
+ /**
+ * Checks to see if there is a return value set that matched the provided parameter values. If there is, it returns it.
+ * Otherwise, it returns the default value provided.
+ *
+ * Also, increments the static method counts.
+ *
+ * @param string $func The name of the method
+ * @param array $params A list of parameter values that were passed to the method name provided
+ * @param mixed|null $default A default value to use if there is no matched return value for this set of parameter values
+ *
+ * @return mixed|null
+ */
+ protected static function _getStaticResponseWithParams(string $func, array $params, $default = null) {
+ self::_incrementCount($func, self::$static_method_counts);
+
+ $ser = serialize($params);
+
+ return (
+ array_key_exists($func, self::$static_return_values) &&
+ array_key_exists($ser, self::$static_return_values[$func])
+ ) ? self::$static_return_values[$func][$ser] : $default;
+ }
+
+ /**
+ * Increments the count for the provided method stored in the provided variable
+ *
+ * @param string $func The name of the method
+ * @param array $stack_count The array that the method count is stored in
+ */
+ protected static function _incrementCount(string $func, array &$stack_count) {
+ if (empty($stack_count[$func])) {
+ $stack_count[$func] = 0;
+ }
+
+ $stack_count[$func]++;
+ }
+}
diff --git a/src/ReflectionHelper.php b/src/ReflectionHelper.php
new file mode 100644
index 0000000..c82b773
--- /dev/null
+++ b/src/ReflectionHelper.php
@@ -0,0 +1,68 @@
+getMethod($method_name);
+ $method->setAccessible(true);
+ if (empty($arguments)) {
+ return $method->invoke($object);
+ } else {
+ return $method->invokeArgs($object, $arguments);
+ }
+ }
+
+ /**
+ * Returns the value of a property on the object that is not a public property.
+ *
+ * If the property has not been initialized, then this method will return NULL
+ *
+ * @param object $object
+ * @param string $property_name
+ *
+ * @return mixed|null
+ *
+ * @throws \ReflectionException
+ */
+ protected function getNonPublicPropertyValue(object $object, string $property_name) {
+ $class = new \ReflectionClass($object);
+ $property = $class->getProperty($property_name);
+ $property->setAccessible(true);
+ if ($property->isInitialized($object)) {
+ return $property->getValue($object);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the value of a property on the object that is not a public property
+ *
+ * @param object $object
+ * @param string $property_name
+ * @param mixed $property_value
+ *
+ * @return void
+ *
+ * @throws \ReflectionException
+ */
+ protected function setNonPublicPropertyValue(object $object, string $property_name, $property_value) {
+ $class = new \ReflectionClass($object);
+ $property = $class->getProperty($property_name);
+ $property->setAccessible(true);
+ $property->setValue($object, $property_value);
+ }
+}
diff --git a/tests.old/bootstrap.php b/tests.old/bootstrap.php
new file mode 100644
index 0000000..489116a
--- /dev/null
+++ b/tests.old/bootstrap.php
@@ -0,0 +1,25 @@
+ $t) {
+ if (!isset($t['file'])) {
+ break;
+ }
+ fwrite(STDERR, "#$pos {$t['file']} on line {$t['line']}\n");
+ }
+ fwrite(STDERR, "###########\n");
+ $args = func_get_args();
+ foreach ($args as $arg) {
+ fwrite(STDERR, trim(var_export($arg, true)) . "\n");
+ }
+ fwrite(STDERR, "###########\n");
+ fwrite(STDERR, "END DEBUG\n\n");
+}
diff --git a/tests.old/etc/config.d/01-config.ini b/tests.old/etc/config.d/01-config.ini
new file mode 100644
index 0000000..0f5119f
--- /dev/null
+++ b/tests.old/etc/config.d/01-config.ini
@@ -0,0 +1,3 @@
+[test]
+test.string = example-config-ini-1
+test.override.me = nope
\ No newline at end of file
diff --git a/tests.old/etc/config.d/02-config.env b/tests.old/etc/config.d/02-config.env
new file mode 100644
index 0000000..ba077db
--- /dev/null
+++ b/tests.old/etc/config.d/02-config.env
@@ -0,0 +1,2 @@
+test_string2=example-config-env-2
+test_override_me=yep
diff --git a/tests.old/etc/config.d/03-config.yaml b/tests.old/etc/config.d/03-config.yaml
new file mode 100644
index 0000000..6e58ce7
--- /dev/null
+++ b/tests.old/etc/config.d/03-config.yaml
@@ -0,0 +1,4 @@
+---
+test:
+ string3: example-config-yaml-3
+...
diff --git a/tests.old/etc/config.d/04-config.json b/tests.old/etc/config.d/04-config.json
new file mode 100644
index 0000000..a8cdaf3
--- /dev/null
+++ b/tests.old/etc/config.d/04-config.json
@@ -0,0 +1,5 @@
+{
+ "test": {
+ "string4": "example-config-json-4"
+ }
+}
\ No newline at end of file
diff --git a/tests.old/etc/config.env b/tests.old/etc/config.env
new file mode 100644
index 0000000..b31017b
--- /dev/null
+++ b/tests.old/etc/config.env
@@ -0,0 +1,3 @@
+test_string=example-env
+test_empty=
+test_zero=0
diff --git a/tests.old/etc/config.ini b/tests.old/etc/config.ini
new file mode 100644
index 0000000..30d0554
--- /dev/null
+++ b/tests.old/etc/config.ini
@@ -0,0 +1,4 @@
+[test]
+test.string = example-ini
+test.empty =
+test.zero = 0
\ No newline at end of file
diff --git a/tests.old/etc/config.json b/tests.old/etc/config.json
new file mode 100644
index 0000000..b61a67f
--- /dev/null
+++ b/tests.old/etc/config.json
@@ -0,0 +1,5 @@
+{
+ "test": {
+ "string": "example-json"
+ }
+}
\ No newline at end of file
diff --git a/tests.old/etc/config.yaml b/tests.old/etc/config.yaml
new file mode 100644
index 0000000..5c2205b
--- /dev/null
+++ b/tests.old/etc/config.yaml
@@ -0,0 +1,4 @@
+---
+test:
+ string: example-yaml
+...
diff --git a/tests.old/etc/config_env_file.ini b/tests.old/etc/config_env_file.ini
new file mode 100644
index 0000000..5a5eb70
--- /dev/null
+++ b/tests.old/etc/config_env_file.ini
@@ -0,0 +1,3 @@
+[test]
+test.string = example-env
+dealnews.test.env.var = something
diff --git a/tests.old/etc/get_config_bad.ini b/tests.old/etc/get_config_bad.ini
new file mode 100644
index 0000000..18bd7e6
--- /dev/null
+++ b/tests.old/etc/get_config_bad.ini
@@ -0,0 +1,3 @@
+{
+ "some": "json"
+}
\ No newline at end of file
diff --git a/tests.old/etc/get_config_bad.json b/tests.old/etc/get_config_bad.json
new file mode 100644
index 0000000..bb7e7aa
--- /dev/null
+++ b/tests.old/etc/get_config_bad.json
@@ -0,0 +1 @@
+some = json
diff --git a/tests.old/etc/get_config_bad.txt b/tests.old/etc/get_config_bad.txt
new file mode 100644
index 0000000..bb7e7aa
--- /dev/null
+++ b/tests.old/etc/get_config_bad.txt
@@ -0,0 +1 @@
+some = json
diff --git a/tests.old/etc/get_config_bad.yaml b/tests.old/etc/get_config_bad.yaml
new file mode 100644
index 0000000..bb7e7aa
--- /dev/null
+++ b/tests.old/etc/get_config_bad.yaml
@@ -0,0 +1 @@
+some = json
diff --git a/tests.old/etc/get_config_test.ini b/tests.old/etc/get_config_test.ini
new file mode 100644
index 0000000..fc5966a
--- /dev/null
+++ b/tests.old/etc/get_config_test.ini
@@ -0,0 +1,2 @@
+[test]
+test.string = example
diff --git a/tests/AssertionStackTest.php b/tests/AssertionStackTest.php
new file mode 100644
index 0000000..8573c20
--- /dev/null
+++ b/tests/AssertionStackTest.php
@@ -0,0 +1,78 @@
+callAssertEqualFromStack(__FUNCTION__, func_get_args());
+ }
+ };
+
+ $this->expectException('\\PHPUnit\\Framework\\ExpectationFailedException');
+ $this->expectExceptionMessage('The number of expected parameters and passed-in parameters does not match for the method that is a mock of stdClass::test()');
+
+ $mock->setTestCaseForMock($this);
+ $mock->addAssertEqualStack('test', [
+ 'var1' => 'foo',
+ 'var2' => 'bar',
+ 'var3' => 'baz',
+ ]);
+
+ $mock->test('foo', 'bar', 'baz', 'hat');
+ }
+
+ /**
+ * Tests to mak sure we're correctly failing on an assertion that one of the parameter values that was passed
+ * to the mock method is not correct
+ */
+ public function testParameterValueFailure() {
+ $mock = new class extends \StdClass {
+ use AssertionStack;
+
+ public function test($var1, $var2, $var3) {
+ $this->callAssertEqualFromStack(__FUNCTION__, func_get_args());
+ }
+ };
+
+ $this->expectException('\\PHPUnit\\Framework\\ExpectationFailedException');
+ $this->expectExceptionMessage('var3 parameter does not have the expected value for the method that is a mock of stdClass::test()');
+
+ $mock->setTestCaseForMock($this);
+ $mock->addAssertEqualStack('test', [
+ 'var1' => 'foo',
+ 'var2' => 'bar',
+ 'var3' => 'baz',
+ ]);
+
+ $mock->test('foo', 'bar', 'hat');
+ }
+
+ public function testSuccess() {
+ $mock = new class extends \StdClass {
+ use AssertionStack;
+
+ public function test($var1, $var2, $var3) {
+ $this->callAssertEqualFromStack(__FUNCTION__, func_get_args());
+ }
+ };
+
+ $mock->setTestCaseForMock($this);
+ $mock->addAssertEqualStack('test', [
+ 'var1' => 'foo',
+ 'var2' => 'bar',
+ 'var3' => 'baz',
+ ]);
+
+ $mock->test('foo', 'bar', 'baz');
+ }
+}
diff --git a/tests/Classes/StaticTestNoParams.php b/tests/Classes/StaticTestNoParams.php
new file mode 100644
index 0000000..61a1b5d
--- /dev/null
+++ b/tests/Classes/StaticTestNoParams.php
@@ -0,0 +1,12 @@
+assertEquals(
+ __DIR__,
+ self::$instance::$test_directory
+ );
+
+ $this->assertEquals(
+ __DIR__ . '/fixtures',
+ self::$instance::$fixture_directory
+ );
+ }
+
+ public function testGetFixtureFile() {
+ self::$instance::setUpBeforeClass();
+ $result = self::$instance->getFixtureFile('foo.json');
+ $this->assertEquals(
+ realpath(__DIR__ . '/fixtures/foo.json'),
+ $result
+ );
+ }
+
+ public function testGetFixtureData() {
+ self::$instance::setUpBeforeClass();
+ $result = self::$instance->getFixtureData('foo.json');
+ $this->assertEquals(
+ '{"foo":true}',
+ $result
+ );
+ }
+
+ public function testGetFixtureJson() {
+ self::$instance::setUpBeforeClass();
+ $result = self::$instance->getFixtureJson('foo.json');
+ $this->assertEquals(
+ ['foo' => true],
+ $result
+ );
+ }
+
+ public function testGetFixtureJsonLines() {
+ self::$instance::setUpBeforeClass();
+ $result = self::$instance->getFixtureJsonLines('foo.jsonl');
+ $this->assertEquals(
+ [
+ ['foo' => true],
+ ['foo' => false],
+ ],
+ $result
+ );
+ }
+}
diff --git a/tests/GuzzleTest.php b/tests/GuzzleTest.php
new file mode 100644
index 0000000..6109fb3
--- /dev/null
+++ b/tests/GuzzleTest.php
@@ -0,0 +1,36 @@
+makeGuzzleMock(
+ [
+ 200,
+ 404,
+ 200,
+ ],
+ [
+ 'foo.json',
+ ['bar' => 2],
+ null,
+ ],
+ $container
+ );
+
+ $this->assertTrue($client instanceof \GuzzleHttp\Client);
+ }
+}
diff --git a/tests/MethodsTest.php b/tests/MethodsTest.php
new file mode 100644
index 0000000..4108ca9
--- /dev/null
+++ b/tests/MethodsTest.php
@@ -0,0 +1,264 @@
+_getNextResponse(__FUNCTION__, 'default');
+ }
+ };
+
+ $child->_addMultipleResponses('test', ['foo']);
+
+ $this->assertTrue(true);
+
+ $bare_mock = new class implements TestInterface {
+ use Methods;
+
+ public function test(): string {
+ return $this->_getNextResponse(__FUNCTION__, 'default');
+ }
+ };
+
+ $bare_mock->_addMultipleResponses('test', ['foo']);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCheckMethodExistsExceptionChild() {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Method test2 not found for DealNews\\TestHelpers\\Tests\\Classes\\TestNoParams when attempting to add a mock response with the Mock\\Methods trait.');
+
+ $child = new class extends TestNoParams {
+ use Methods;
+
+ public function test(): string {
+ return $this->_getNextResponse(__FUNCTION__, 'default');
+ }
+ };
+
+ $child->_addMultipleResponses('test2', ['foo']);
+ }
+
+ public function testCheckMethodExistsExceptionBare() {
+ $this->expectException(\InvalidArgumentException::class);
+ // expectExceptionMessage checks the string is contained, not an exact match
+ $this->expectExceptionMessage('Method test2 not found');
+
+ $bare_mock = new class implements TestInterface {
+ use Methods;
+
+ public function test(): string {
+ return $this->_getNextResponse(__FUNCTION__, 'default');
+ }
+ };
+
+ $bare_mock->_addMultipleResponses('test2', ['foo']);
+ }
+
+ public function testCheckMethodExistsExceptionBareNoInterface() {
+ $this->expectException(\LogicException::class);
+ // expectExceptionMessage checks the string is contained, not an exact match
+ $this->expectExceptionMessage('class@anonymous');
+
+ $bare_mock = new class {
+ use Methods;
+
+ public function test(): string {
+ return $this->_getNextResponse(__FUNCTION__, 'default');
+ }
+ };
+
+ $bare_mock->_addMultipleResponses('test', ['foo']);
+ }
+
+ public function testGetNextResponse() {
+ $object = new class extends TestNoParams {
+ use Methods;
+
+ public function test(): string {
+ return $this->_getNextResponse(__FUNCTION__, 'default');
+ }
+ };
+
+ $object->_addMultipleResponses(
+ 'test',
+ [
+ 'true',
+ 'false',
+ 'false',
+ 'true',
+ 'true',
+ ]
+ );
+
+ $this->assertEquals('true', $object->test());
+ $this->assertEquals('false', $object->test());
+ $this->assertEquals('false', $object->test());
+ $this->assertEquals('true', $object->test());
+ $this->assertEquals('true', $object->test());
+
+ $this->assertEquals(5, $object->method_counts['test']);
+
+ // test default value
+ $this->assertEquals('default', $object->test());
+ $this->assertEquals(6, $object->method_counts['test']);
+
+ // test _addMultipleMethodResponses
+ $object->_addMultipleMethodResponses([
+ 'test' => [
+ 'some',
+ 'different',
+ 'results',
+ 'than',
+ 'before',
+ ],
+ ]);
+
+ $this->assertEquals('some', $object->test());
+ $this->assertEquals('different', $object->test());
+ $this->assertEquals('results', $object->test());
+ $this->assertEquals('than', $object->test());
+ $this->assertEquals('before', $object->test());
+
+ $this->assertEquals(5, $object->method_counts['test']);
+ }
+
+ public function testGetResponseWithParams() {
+ $object = new class extends TestOneParam {
+ use Methods;
+
+ public function test(string $foo): string {
+ return $this->_getResponseWithParams(__FUNCTION__, func_get_args(), 'default');
+ }
+ };
+
+ $object->_addReturnValueWithParams(
+ 'test',
+ ['true_test'],
+ 'true'
+ );
+
+ $object->_addReturnValueWithParams(
+ 'test',
+ ['false_test'],
+ 'false'
+ );
+
+ $this->assertEquals('false', $object->test('false_test'));
+ $this->assertEquals('true', $object->test('true_test'));
+ $this->assertEquals('default', $object->test('default_test'));
+
+ $this->assertEquals(3, $object->method_counts['test']);
+ }
+
+ public function testGetNextStaticResponse() {
+ $object = new class extends StaticTestNoParams {
+ use Methods;
+
+ public static function test(): string {
+ return self::_getNextStaticResponse(__FUNCTION__, 'default');
+ }
+ };
+
+ $object::_addMultipleStaticResponses(
+ 'test',
+ [
+ 'true',
+ 'false',
+ 'false',
+ 'true',
+ 'true',
+ 'false', // sixth one should be used due to reset below
+ ]
+ );
+
+ $this->assertEquals('true', $object::test());
+ $this->assertEquals('false', $object::test());
+ $this->assertEquals('false', $object::test());
+ $this->assertEquals('true', $object::test());
+ $this->assertEquals('true', $object::test());
+
+ $this->assertEquals(5, $object::$static_method_counts['test']);
+
+ // test reset
+ $object::_resetStaticResponses();
+ $this->assertEquals('default', $object::test());
+ $this->assertEquals(1, $object::$static_method_counts['test']);
+
+ // test default value
+ $this->assertEquals('default', $object::test());
+ $this->assertEquals(2, $object::$static_method_counts['test']);
+
+ // test _addMultipleStaticMethodResponses
+ $object::_addMultipleStaticMethodResponses([
+ 'test' => [
+ 'some',
+ 'different',
+ 'results',
+ 'than',
+ 'before',
+ ],
+ ]);
+
+ $this->assertEquals('some', $object::test());
+ $this->assertEquals('different', $object::test());
+ $this->assertEquals('results', $object::test());
+ $this->assertEquals('than', $object::test());
+ $this->assertEquals('before', $object::test());
+
+ $this->assertEquals(5, $object::$static_method_counts['test']);
+ }
+
+ public function testGetStaticResponseWithParams() {
+ $object = new class extends StaticTestOneParam {
+ use Methods;
+
+ public static function test(string $foo): string {
+ return self::_getStaticResponseWithParams(__FUNCTION__, func_get_args(), 'default');
+ }
+ };
+
+ $object::_addStaticReturnValueWithParams(
+ 'test',
+ ['true_test'],
+ 'true'
+ );
+
+ $object::_addStaticReturnValueWithParams(
+ 'test',
+ ['false_test'],
+ 'false'
+ );
+
+ $object::_addStaticReturnValueWithParams(
+ 'test',
+ ['reset_test'],
+ 'reset'
+ );
+
+ $this->assertEquals('false', $object::test('false_test'));
+ $this->assertEquals('true', $object::test('true_test'));
+ $this->assertEquals(2, $object::$static_method_counts['test']);
+
+ //test reset
+ $object::_resetStaticResponses();
+ $this->assertEquals('default', $object::test('reset_test'));
+
+ //test default
+ $this->assertEquals('default', $object::test('default_test'));
+
+ $this->assertEquals(2, $object::$static_method_counts['test']);
+ }
+}
diff --git a/tests/ReflectionHelperTest.php b/tests/ReflectionHelperTest.php
new file mode 100644
index 0000000..3504a36
--- /dev/null
+++ b/tests/ReflectionHelperTest.php
@@ -0,0 +1,92 @@
+executeNonPublicMethod($test, 'aProtectedMethod');
+ $this->assertEquals(null, $result, 'aProtectedMethod with no arguments passed did not return the expected value');
+
+ // protected method, with a string argument passed
+ $result = $this->executeNonPublicMethod($test, 'aProtectedMethod', ['hello world']);
+ $this->assertEquals('hello world', $result, 'aProtectedMethod with a string argument passed did not return the expected value');
+
+ // private method, no arguments passed
+ $result = $this->executeNonPublicMethod($test, 'aPrivateMethod');
+ $this->assertEquals(null, $result, 'aPrivateMethod with no arguments passed did not return the expected value');
+
+ // private method, with a string argument passed
+ $result = $this->executeNonPublicMethod($test, 'aPrivateMethod', ['hello world']);
+ $this->assertEquals('hello world', $result, 'aPrivateMethod with a string argument passed did not return the expected value');
+ }
+
+ public function testGetNonPublicPropertyValue() {
+ $test = new class {
+ protected string $a_protected_property = 'hello world, protected';
+
+ private string $a_private_property = 'hello world, private';
+
+ protected string $uninitialized;
+ };
+
+ $result = $this->getNonPublicPropertyValue($test, 'a_protected_property');
+ $this->assertEquals('hello world, protected', $result, 'Did not get the expected value for a_protected_property');
+
+ $result = $this->getNonPublicPropertyValue($test, 'a_private_property');
+ $this->assertEquals('hello world, private', $result, 'Did not get the expected value for a_private_property');
+
+ $result = $this->getNonPublicPropertyValue($test, 'uninitialized');
+ $this->assertEquals(null, $result, 'Did not get the expected value for uninitialized');
+ }
+
+ public function testSetNonPublicPropertyValue() {
+ $test = new class {
+ protected string $a_protected_property = 'hello world, protected';
+
+ private string $a_private_property = 'hello world, private';
+
+ protected string $uninitialized;
+
+ public function getAProtectedPropertyValue() {
+ return $this->a_protected_property;
+ }
+
+ public function getAPrivatePropertyValue() {
+ return $this->a_private_property;
+ }
+
+ public function getUninitializedValue() {
+ if (!isset($this->uninitialized)) {
+ // not initialized, yet
+ return null;
+ } else {
+ return $this->uninitialized;
+ }
+ }
+ };
+
+ $this->setNonPublicPropertyValue($test, 'a_protected_property', 'different value, protected');
+ $this->assertEquals('different value, protected', $test->getAProtectedPropertyValue(), 'Did not get the expected value for a_protected_property');
+
+ $this->setNonPublicPropertyValue($test, 'a_private_property', 'different value, private');
+ $this->assertEquals('different value, private', $test->getAPrivatePropertyValue(), 'Did not get the expected value for a_private_property');
+
+ $this->setNonPublicPropertyValue($test, 'uninitialized', 'different value, uninitialized');
+ $this->assertEquals('different value, uninitialized', $test->getUninitializedValue(), 'Did not get the expected value for uninitialized');
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..a36d5bc
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,31 @@
+ $t) {
+ if (!isset($t['file'])) {
+ break;
+ }
+ fwrite(STDERR, "#$pos {$t['file']} on line {$t['line']}\n");
+ }
+ fwrite(STDERR, "###########\n");
+ $args = func_get_args();
+ foreach ($args as $arg) {
+ fwrite(STDERR, trim(var_export($arg, true)) . "\n");
+ }
+ fwrite(STDERR, "###########\n");
+ fwrite(STDERR, "END DEBUG\n\n");
+}
diff --git a/tests/fixtures/foo.json b/tests/fixtures/foo.json
new file mode 100644
index 0000000..b435d57
--- /dev/null
+++ b/tests/fixtures/foo.json
@@ -0,0 +1 @@
+{"foo":true}
\ No newline at end of file
diff --git a/tests/fixtures/foo.jsonl b/tests/fixtures/foo.jsonl
new file mode 100644
index 0000000..e644e02
--- /dev/null
+++ b/tests/fixtures/foo.jsonl
@@ -0,0 +1,2 @@
+{"foo":true}
+{"foo":false}