diff --git a/.circleci/config.yml b/.circleci/config.yml index 65b0805..317b113 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,30 +1,180 @@ -version: 2.0 -jobs: - build: - machine: - image: ubuntu-2004:202010-01 - environment: - CC_TEST_REPORTER_ID: 2ecadaa1fe4b1917c00e1e9e79591bfa4dad375145696201016a1fd090e33577 - working_directory: ~/repo +# PHPUnit Composer min/max test. +# TODO: Make our own orb out of this. + +version: 2.1 +orbs: + php: circleci/php@1.1.0 + +commands: + update-packages: + description: | + Update your composer packages with automated caching and best practices applied. + parameters: + app-dir: + default: ~/project + description: Path to the directory containing your composer.json file. Not needed if composer.json lives in the root. + type: string + cache-files-dir: + default: /home/circleci/.composer/cache/files + description: Absolute path to the file cache folder. This should be inline with "composer global config cache-files-dir --absolute". + type: string + cache-key: + default: composer.lock + description: If this file is updated a new cache bucket will be created. Recommended to use composer.lock. Use composer.json when composer.lock is absent. + type: string + cache-version: + default: v1 + description: Change the default cache version if you need to clear the cache for any reason. + type: string + install-flags: + default: --no-interaction --prefer-dist + description: | + By default, packages will be installed with "composer install --no-interaction --prefer-dist", use this to override the standard install flags. + type: string + vendor-dir: + default: vendor + description: Relative path to the vendor folder. Relative to "app-dir". This should be inline with "composer config vendor-dir". + type: string + with-cache: + default: true + description: Enable automatic caching of your dependencies for increased speed. + type: boolean steps: - - checkout + - when: + condition: << parameters.with-cache >> + steps: + - restore_cache: + keys: + - composer-deps-<>-{{ checksum "<>/<>" }} - run: - name: Setup DDEV command: | - curl -LO https://raw.githubusercontent.com/drud/ddev/master/scripts/install_ddev.sh && bash install_ddev.sh + if [ ! -f "composer.json" ] && [ ! -f "composer.lock" ]; then + echo + echo "---" + echo "Unable to find your composer.json and composer.lock files. Did you forget to set the app-dir parameter?" + echo "---" + echo + echo "Current directory: $(pwd)" + echo + echo + echo "List directory: " + echo + ls + exit 1 + fi + name: Verify composer.json and/or composer.lock exist + working_directory: <> + - run: + command: composer update <> + name: Updating Composer Packages + working_directory: <> + - when: + condition: << parameters.with-cache >> + steps: + - save_cache: + key: composer-deps-<>-{{ checksum "<>/<>" }} + paths: + - <>/<> + - <> + install-xdebug: + steps: + - run: + name: Install XDebug + command: sudo -E install-php-extensions xdebug && sudo -E docker-php-ext-enable xdebug + + install-cc-test-reporter: + # TODO: Parameterize location. + steps: - run: - name: Setup Code Climate test-reporter + name: Install Codeclimate test reporter command: | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter chmod +x ./cc-test-reporter - - run: - name: Run tests - command: | - pwd - ddev start - ddev xdebug - ddev composer install - ./cc-test-reporter before-build - ddev exec ./vendor/bin/phpunit --testsuite all --coverage-clover clover.xml - sed -i 's+/var/www/html/+/home/circleci/repo/+g' clover.xml - ./cc-test-reporter after-build --coverage-input-type clover --exit-code $? \ No newline at end of file + + run-phpunit-tests: + description: | + Run PHPUnit tests. + parameters: + app-dir: + default: ~/project + description: Path to the directory containing your composer.json file. Not needed if composer.json lives in the root. + type: string + install-flags: + default: "" + description: Arguments to `composer update`. + type: string + test-command: + default: test + description: The name of the script within your composer.json which will run your tests. + type: string + report-to-codeclimate: + type: boolean + default: false + description: Report coverage info to Codeclimate. + steps: + - checkout + - update-packages: + app-dir: <> + cache-key: composer.json + install-flags: <> + - when: + condition: <> + steps: + - install-xdebug + - install-cc-test-reporter + - run: | + ./cc-test-reporter before-build + XDEBUG_MODE=coverage composer <> -- --coverage-clover clover.xml + ./cc-test-reporter after-build --coverage-input-type clover --exit-code $? + - when: + condition: + not: <> + steps: + - run: | + XDEBUG_MODE=off composer <> + +jobs: + matrix-conditions: + description: Run tests for matrix + executor: + name: php/default + tag: << parameters.version >> + parameters: + version: + default: "7.4" + description: The `cimg/php` Docker image version tag. + type: string + install-flags: + default: "" + description: Arguments to `composer update`. + type: string + environment: + CC_TEST_REPORTER_ID: 2ecadaa1fe4b1917c00e1e9e79591bfa4dad375145696201016a1fd090e33577 + steps: + - when: + condition: + and: + - equal: [ "8.1", <> ] + - equal: [ "", <> ] + steps: + - run-phpunit-tests: + report-to-codeclimate: true + install-flags: << parameters.install-flags >> + - when: + condition: + not: + and: + - equal: [ "8.1", <> ] + - equal: [ "", <> ] + steps: + - run-phpunit-tests: + install-flags: << parameters.install-flags >> + +workflows: + all-tests: + jobs: + - matrix-conditions: + matrix: + parameters: + version: ["8.1", "8.0", "7.4"] + install-flags: ["", "--prefer-lowest"] diff --git a/.ddev/config.yaml b/.ddev/config.yaml deleted file mode 100644 index 61181e4..0000000 --- a/.ddev/config.yaml +++ /dev/null @@ -1,171 +0,0 @@ -name: csv-parser -type: php -docroot: "" -php_version: "7.3" -webserver_type: nginx-fpm -router_http_port: "80" -router_https_port: "443" -xdebug_enabled: false -additional_hostnames: [] -additional_fqdns: [] -mariadb_version: "10.2" -mysql_version: "" -provider: default -use_dns_when_possible: true -composer_version: "" - - -# This config.yaml was created with ddev version v1.16.5 -# webimage: drud/ddev-webserver:v1.16.3 -# dbimage: drud/ddev-dbserver-mariadb-10.2:v1.16.0 -# dbaimage: phpmyadmin:5 -# However we do not recommend explicitly wiring these images into the -# config.yaml as they may break future versions of ddev. -# You can update this config.yaml using 'ddev config'. - -# Key features of ddev's config.yaml: - -# name: # Name of the project, automatically provides -# http://projectname.ddev.site and https://projectname.ddev.site - -# type: # drupal6/7/8, backdrop, typo3, wordpress, php - -# docroot: # Relative path to the directory containing index.php. - -# php_version: "7.3" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4" "8.0" - -# You can explicitly specify the webimage, dbimage, dbaimage lines but this -# is not recommended, as the images are often closely tied to ddev's' behavior, -# so this can break upgrades. - -# webimage: # nginx/php docker image. -# dbimage: # mariadb docker image. -# dbaimage: - -# mariadb_version and mysql_version -# ddev can use many versions of mariadb and mysql -# However these directives are mutually exclusive -# mariadb_version: 10.2 -# mysql_version: 8.0 - -# router_http_port: # Port to be used for http (defaults to port 80) -# router_https_port: # Port for https (defaults to 443) - -# xdebug_enabled: false # Set to true to enable xdebug and "ddev start" or "ddev restart" -# Note that for most people the commands -# "ddev xdebug" to enable xdebug and "ddev xdebug off" to disable it work better, -# as leaving xdebug enabled all the time is a big performance hit. - -# webserver_type: nginx-fpm # or apache-fpm - -# timezone: Europe/Berlin -# This is the timezone used in the containers and by PHP; -# it can be set to any valid timezone, -# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones -# For example Europe/Dublin or MST7MDT - -# composer_version: "2" -# if composer_version:"" it will use the current ddev default composer release. -# It can also be set to "1", to get most recent composer v1 -# or "2" for most recent composer v2. -# It can be set to any existing specific composer version. -# After first project 'ddev start' this will not be updated until it changes - -# additional_hostnames: -# - somename -# - someothername -# would provide http and https URLs for "somename.ddev.site" -# and "someothername.ddev.site". - -# additional_fqdns: -# - example.com -# - sub1.example.com -# would provide http and https URLs for "example.com" and "sub1.example.com" -# Please take care with this because it can cause great confusion. - -# upload_dir: custom/upload/dir -# would set the destination path for ddev import-files to custom/upload/dir. - -# working_dir: -# web: /var/www/html -# db: /home -# would set the default working directory for the web and db services. -# These values specify the destination directory for ddev ssh and the -# directory in which commands passed into ddev exec are run. - -# omit_containers: [db, dba, ddev-ssh-agent] -# Currently only these containers are supported. Some containers can also be -# omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit -# the "db" container, several standard features of ddev that access the -# database container will be unusable. - -# nfs_mount_enabled: false -# Great performance improvement but requires host configuration first. -# See https://ddev.readthedocs.io/en/stable/users/performance/#using-nfs-to-mount-the-project-into-the-container - -# host_https_port: "59002" -# The host port binding for https can be explicitly specified. It is -# dynamic unless otherwise specified. -# This is not used by most people, most people use the *router* instead -# of the localhost port. - -# host_webserver_port: "59001" -# The host port binding for the ddev-webserver can be explicitly specified. It is -# dynamic unless otherwise specified. -# This is not used by most people, most people use the *router* instead -# of the localhost port. - -# host_db_port: "59002" -# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic -# unless explicitly specified. - -# phpmyadmin_port: "8036" -# phpmyadmin_https_port: "8037" -# The PHPMyAdmin ports can be changed from the default 8036 and 8037 - -# mailhog_port: "8025" -# mailhog_https_port: "8026" -# The MailHog ports can be changed from the default 8025 and 8026 - -# webimage_extra_packages: [php7.3-tidy, php-bcmath] -# Extra Debian packages that are needed in the webimage can be added here - -# dbimage_extra_packages: [telnet,netcat] -# Extra Debian packages that are needed in the dbimage can be added here - -# use_dns_when_possible: true -# If the host has internet access and the domain configured can -# successfully be looked up, DNS will be used for hostname resolution -# instead of editing /etc/hosts -# Defaults to true - -# project_tld: ddev.site -# The top-level domain used for project URLs -# The default "ddev.site" allows DNS lookup via a wildcard -# If you prefer you can change this to "ddev.local" to preserve -# pre-v1.9 behavior. - -# ngrok_args: --subdomain mysite --auth username:pass -# Provide extra flags to the "ngrok http" command, see -# https://ngrok.com/docs#http or run "ngrok http -h" - -# disable_settings_management: false -# If true, ddev will not create CMS-specific settings files like -# Drupal's settings.php/settings.ddev.php or TYPO3's AdditionalSettings.php -# In this case the user must provide all such settings. - -# no_project_mount: false -# (Experimental) If true, ddev will not mount the project into the web container; -# the user is responsible for mounting it manually or via a script. -# This is to enable experimentation with alternate file mounting strategies. -# For advanced users only! - -# provider: default # Currently either "default" or "pantheon" -# -# Many ddev commands can be extended to run tasks before or after the -# ddev command is executed, for example "post-start", "post-import-db", -# "pre-composer", "post-composer" -# See https://ddev.readthedocs.io/en/stable/users/extending-commands/ for more -# information on the commands that can be extended and the tasks you can define -# for them. Example: -#hooks: diff --git a/.ddev/docker-compose.environment.yml b/.ddev/docker-compose.environment.yml deleted file mode 100644 index 3de6298..0000000 --- a/.ddev/docker-compose.environment.yml +++ /dev/null @@ -1,4 +0,0 @@ -services: - web: - environment: - XDEBUG_MODE: 'coverage' diff --git a/.ddev/php/xdebug_remote_port.ini b/.ddev/php/xdebug_remote_port.ini deleted file mode 100644 index da2b68c..0000000 --- a/.ddev/php/xdebug_remote_port.ini +++ /dev/null @@ -1,4 +0,0 @@ -[xdebug] -xdebug.mode = coverage -xdebug.start_with_request = yes -xdebug.discover_client_host = 1 diff --git a/.gitignore b/.gitignore index 92d2346..e8af8be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.ddev +.idea vendor composer.lock /html \ No newline at end of file diff --git a/composer.json b/composer.json index 017e331..024d1e3 100644 --- a/composer.json +++ b/composer.json @@ -8,18 +8,32 @@ "email": "fmizzell.dev@gmail.com" } ], + "require": { + "php": ">=7.4", + "ext-json": "*", + "fmizzell/maquina": "^1.1.1", + "getdkan/contracts": "^1.1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6", + "rector/rector": "^0.15.17", + "squizlabs/php_codesniffer": "^3.7" + }, "autoload": { "psr-4": { - "CsvParser\\": "src/", - "CsvParserTest\\": "test/" + "CsvParser\\": "src/" } }, - "require": { - "fmizzell/maquina": "^1.0.1", - "getdkan/contracts": "^1.0.0", - "ext-json": "*" + "autoload-dev": { + "psr-4": { + "CsvParserTest\\": "test/" + } }, - "require-dev": { - "phpunit/phpunit": ">=7.5 <=9.5" + "scripts": { + "phpcbf": "./vendor/bin/phpcbf", + "phpcs": "./vendor/bin/phpcs", + "rector": "./vendor/bin/rector process", + "rector-dry-run": "./vendor/bin/rector process --dry-run", + "test": "./vendor/bin/phpunit --testsuite all" } } diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..45200cd --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,14 @@ + + + + + PHP CodeSniffer configuration for GetDKAN. + + src + test + rector.php + + + + + diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..17be9b8 --- /dev/null +++ b/rector.php @@ -0,0 +1,22 @@ +paths([ + __DIR__ . '/src', + __DIR__ . '/test', + ]); + + $rectorConfig->sets([ + LevelSetList::UP_TO_PHP_74, + ]); + + $rectorConfig->skip([ + JsonThrowOnErrorRector::class, + ]); +}; diff --git a/src/Parser/Csv.php b/src/Parser/Csv.php index d2d2619..7dac88e 100644 --- a/src/Parser/Csv.php +++ b/src/Parser/Csv.php @@ -12,18 +12,18 @@ class Csv implements ParserInterface, \JsonSerializable private $delimiter; private $quote; private $escape; - private $recordEnd; + private array $recordEnd; private $records; private $fields; - private $field; + private string $field; public $machine; private $lastCharType; - private $quoted = false; + private bool $quoted = false; - private $trailingDelimiter = false; + private bool $trailingDelimiter = false; public function activateTrailingDelimiter() { @@ -179,6 +179,7 @@ private function processTransitionHelper($endState, $input) } } + #[\ReturnTypeWillChange] public function jsonSerialize() { return (object) [ diff --git a/src/Parser/StateMachine.php b/src/Parser/StateMachine.php index bda82b2..4e262d2 100644 --- a/src/Parser/StateMachine.php +++ b/src/Parser/StateMachine.php @@ -5,25 +5,25 @@ class StateMachine extends MachineOfMachines { - const STATE_NEW_FIELD = "s_new_field"; - const STATE_CAPTURE = "s_capture"; - const STATE_NO_CAPTURE = "s_no_capture"; - const STATE_ESCAPE = "s_escape"; - const STATE_RECORD_END = "s_record_end"; - const STATE_REDUNDANT_RECORD_END = "s_redundant_record_end"; - - const STATE_QUOTE_INITIAL = "s_q_initial"; - const STATE_QUOTE_FINAL = "s_q_final"; - const STATE_QUOTE_CAPTURE = "s_q_capture"; - const STATE_QUOTE_ESCAPE = "s_q_escape"; - const STATE_QUOTE_ESCAPE_QUOTE = "s_q_escape_quote"; - - const CHAR_TYPE_DELIMITER = "c_delimiter"; - const CHAR_TYPE_QUOTE = "c_quote"; - const CHAR_TYPE_ESCAPE = "c_escape"; - const CHAR_TYPE_RECORD_END = "c_record_end"; - const CHAR_TYPE_BLANK = "c_blank"; - const CHAR_TYPE_OTHER = "c_other"; + public const STATE_NEW_FIELD = "s_new_field"; + public const STATE_CAPTURE = "s_capture"; + public const STATE_NO_CAPTURE = "s_no_capture"; + public const STATE_ESCAPE = "s_escape"; + public const STATE_RECORD_END = "s_record_end"; + public const STATE_REDUNDANT_RECORD_END = "s_redundant_record_end"; + + public const STATE_QUOTE_INITIAL = "s_q_initial"; + public const STATE_QUOTE_FINAL = "s_q_final"; + public const STATE_QUOTE_CAPTURE = "s_q_capture"; + public const STATE_QUOTE_ESCAPE = "s_q_escape"; + public const STATE_QUOTE_ESCAPE_QUOTE = "s_q_escape_quote"; + + public const CHAR_TYPE_DELIMITER = "c_delimiter"; + public const CHAR_TYPE_QUOTE = "c_quote"; + public const CHAR_TYPE_ESCAPE = "c_escape"; + public const CHAR_TYPE_RECORD_END = "c_record_end"; + public const CHAR_TYPE_BLANK = "c_blank"; + public const CHAR_TYPE_OTHER = "c_other"; public function __construct() { diff --git a/test/CsvParserTest.php b/test/CsvParserTest.php index 7563f54..37c5911 100644 --- a/test/CsvParserTest.php +++ b/test/CsvParserTest.php @@ -3,8 +3,14 @@ namespace CsvParserTest; use CsvParser\Parser\Csv; - -class CsvParserTest extends \PHPUnit\Framework\TestCase +use CsvParser\Parser\StateMachine; +use PHPUnit\Framework\TestCase; + +/** + * @covers \CsvParser\Parser\Csv + * @coversDefaultClass \CsvParser\Parser\Csv + */ +class CsvParserTest extends TestCase { private function parse($string) @@ -15,14 +21,6 @@ private function parse($string) return $parser; } - private function assertNumberOfFieldsAndValues($record, $values) - { - $this->assertEquals(count($values), count($record)); - foreach ($record as $key => $value) { - $this->assertEquals($values[$key], $value); - } - } - public function testEmptyString() { $this->expectExceptionMessage("The CSV parser can not parse empty chunks."); @@ -34,7 +32,7 @@ public function testJustDelimiters() $parser = $this->parse(',,'); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -43,7 +41,7 @@ public function testEmptyNewLineString() $parser = $this->parse("\n"); $record = $parser->getRecord(); $values = ['']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -52,10 +50,10 @@ public function testJustDelimitersNewLineString() $parser = $this->parse(",,\n,,"); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -64,7 +62,7 @@ public function testBlankString() $parser = $this->parse(' '); $record = $parser->getRecord(); $values = ['']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -73,7 +71,7 @@ public function testBlankJustDelimiters() $parser = $this->parse(' , , '); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -82,10 +80,10 @@ public function testBlankNewLineString() $parser = $this->parse(" \n "); $record = $parser->getRecord(); $values = ['']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -94,10 +92,10 @@ public function testBlankJustDelimitersNewLineString() $parser = $this->parse(" , , \n , , "); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -106,7 +104,7 @@ public function testOther() $parser = $this->parse('A'); $record = $parser->getRecord(); $values = ['A']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -115,7 +113,7 @@ public function testOtherJustDelimiters() $parser = $this->parse('A,B,C'); $record = $parser->getRecord(); $values = ['A', 'B', 'C']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -124,10 +122,10 @@ public function testOtherNewLineString() $parser = $this->parse("A\nB"); $record = $parser->getRecord(); $values = ['A']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['B']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -136,10 +134,10 @@ public function testOtherJustDelimitersNewLineString() $parser = $this->parse("A,B,C\nD,E,F"); $record = $parser->getRecord(); $values = ['A', 'B', 'C']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['D', 'E', 'F']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -148,7 +146,7 @@ public function testOtherBlank() $parser = $this->parse(' A B '); $record = $parser->getRecord(); $values = ['A B']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -157,7 +155,7 @@ public function testOtherBlankJustDelimiters() $parser = $this->parse(' A B ,B C , CD'); $record = $parser->getRecord(); $values = ['A B', 'B C', 'CD']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -166,10 +164,10 @@ public function testOtherBlankNewLineString() $parser = $this->parse("A B \n B C"); $record = $parser->getRecord(); $values = ['A B']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['B C']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -178,10 +176,10 @@ public function testOtherBlankJustDelimitersNewLineString() $parser = $this->parse(" AB,B C \n D E,E F "); $record = $parser->getRecord(); $values = ['AB', 'B C']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['D E', 'E F']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -190,7 +188,7 @@ public function testOtherBlankEscape() $parser = $this->parse(' A \\' . "\n" . 'B\, '); $record = $parser->getRecord(); $values = ["A \nB,"]; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -199,7 +197,7 @@ public function testOtherBlankEscapeJustDelimiters() $parser = $this->parse(' A \\\B ,B \\' . "\n" . ' C , CD'); $record = $parser->getRecord(); $values = ['A \\B', "B \n C", 'CD']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -208,10 +206,10 @@ public function testOtherBlankEscapeNewLineString() $parser = $this->parse('A B \\' . "\n \n" . ' \\, B C '); $record = $parser->getRecord(); $values = ["A B \n"]; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = [', B C']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -220,10 +218,10 @@ public function testOtherBlankEscapeJustDelimitersNewLineString() $parser = $this->parse(' A \\' . "\n" . ' B,B C ' . "\n" . ' \\\D E\,,E F '); $record = $parser->getRecord(); $values = ["A \n B", 'B C']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['\D E,', 'E F']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -232,7 +230,7 @@ public function testQuotes() $parser = $this->parse('""'); $record = $parser->getRecord(); $values = ['']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -241,7 +239,7 @@ public function testQuoteDelimiters() $parser = $this->parse('" A B " , " B ' . "\n" . ' C"'); $record = $parser->getRecord(); $values = [' A B ', " B \n C"]; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -250,10 +248,10 @@ public function testQuoteNewLineString() $parser = $this->parse(' " A B ' . "\n" . '" ' . "\n" . ' ", B \" C "'); $record = $parser->getRecord(); $values = [" A B \n"]; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = [', B " C ']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -262,10 +260,10 @@ public function testQuoteJustDelimitersNewLineString() $parser = $this->parse(' "A '. "\n" . ' B" , "B C" ' . "\n" . ' "\\\D E," , "E F" '); $record = $parser->getRecord(); $values = ["A \n B", 'B C']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['\D E,', 'E F']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -274,17 +272,17 @@ public function testDoubleQuoteEscaping() $parser = $this->parse('"S ""H"""'); $record = $parser->getRecord(); $values = ['S "H"']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); $parser = $this->parse('"S ""H"" S"'); $record = $parser->getRecord(); $values = ['S "H" S']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); $parser = $this->parse('"""H"" S"'); $record = $parser->getRecord(); $values = ['"H" S']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -303,7 +301,7 @@ public function testBrokenLookAhead() $parser->finish(); $record = $parser->getRecord(); $values = ['S "H"']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -312,10 +310,10 @@ public function testTrailingDelimiter() $parser = $this->parse('H,F,' . "\n" . 'G,B,'); $record = $parser->getRecord(); $values = ['H', 'F', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['G', 'B', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); $parser = Csv::getParser(",", "\"", "\\", ["\r", "\n"]); @@ -325,10 +323,10 @@ public function testTrailingDelimiter() $parser->finish(); $record = $parser->getRecord(); $values = ['H', 'F']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['G', 'B']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -337,25 +335,25 @@ public function testMultiEnd() $parser = $this->parse('H,F' . "\n\r" . 'G,B'); $record = $parser->getRecord(); $values = ['H', 'F']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['G', 'B']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); $parser = $this->parse('H,F' . "\n\r\n" . 'G,B'); $record = $parser->getRecord(); $values = ['H', 'F']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['G', 'B']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); $parser = $this->parse("a,b\r\n"); $record = $parser->getRecord(); $values = ['a', 'b']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -384,10 +382,10 @@ public function testEndingWithNewLineString() $parser = $this->parse(",,\n,,\n"); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -396,10 +394,10 @@ public function testEndingWithWindowsNewLineString() $parser = $this->parse(",,\r\n,,\r\n"); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -408,10 +406,10 @@ public function testEndingWithMultipleNewLinesString() $parser = $this->parse(",,\n,,\n\n\n"); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -420,10 +418,10 @@ public function testEndingWithMultipleWindowsNewLinesString() $parser = $this->parse(",,\r\n,,\r\n\r\n\r\n"); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $record = $parser->getRecord(); $values = ['', '', '']; - $this->assertNumberOfFieldsAndValues($record, $values); + $this->assertEquals($values, $record); $this->assertNull($parser->getRecord()); } @@ -432,7 +430,33 @@ public function testEmptyWindowsNewLineString() $parser = $this->parse("\r\n"); $record = $parser->getRecord(); $values = ['']; - $this->assertNumberOfFieldsAndValues($record, $values); - $this->assertNull($parser->getRecord()); + $this->assertEquals($values, $record); + $this->assertNull($parser->getRecord()); + } + + /** + * @covers ::finish + */ + public function testFinishException() + { + $this->expectExceptionMessage('Machine did not halt'); + + $csv = Csv::getParser(); + // Set lastCharType to StateMachine::CHAR_TYPE_RECORD_END. Use + // reflection since it's not public and doesn't have a setter. + $last_char_type = new \ReflectionProperty(Csv::class, 'lastCharType'); + $last_char_type->setAccessible(true); + $last_char_type->setValue($csv, StateMachine::CHAR_TYPE_RECORD_END); + // Mock a machine. + $machine = $this->getMockBuilder(StateMachine::class) + ->disableOriginalConstructor() + ->onlyMethods(['isCurrentlyAtAnEndState']) + ->getMock(); + $machine->method('isCurrentlyAtAnEndState') + ->willReturn(false); + // Since ::$machine is public, we can set it easily. + $csv->machine = $machine; + // Finally call it. + $csv->finish(); } } diff --git a/test/FileTest.php b/test/FileTest.php index 81c39fa..de9f6bd 100644 --- a/test/FileTest.php +++ b/test/FileTest.php @@ -2,16 +2,19 @@ namespace CsvParserTest; -class FileTest extends \PHPUnit\Framework\TestCase +use CsvParser\Parser\Csv; +use PHPUnit\Framework\TestCase; + +class FileTest extends TestCase { public function test() { - $parser = \CsvParser\Parser\Csv::getParser(); + $parser = Csv::getParser(); $parser->feed(file_get_contents(__DIR__ . "/data/countries.csv")); $parser->finish(); $records = $parser->getRecords(); - $this->assertEquals(5, count($records)); + $this->assertCount(5, $records); } }