From 2a47cb96317808e89d28eaca2c10549e00bf192f Mon Sep 17 00:00:00 2001
From: Christian Kuhn <lolli@schwarzbu.ch>
Date: Thu, 31 Mar 2022 17:48:20 +0200
Subject: [PATCH] [TASK] runTests.sh: Remove .csv integrity handling

When core functional tests started to heavily rely
on CSV based import- and assertion files, we found
that editing such .csv files in Microsoft Excel leads
to warnings if the number of columns is not identical
for each row.

Script checkIntegrityCsvFixtures.php has then been
established to verify all rows of .csv fixture files
have the same amount of fields per file, and has been
enabled as CI job to ensure all existing fixture
files follow this.

Nowadays, this restriction feels archaic: Devs actively
working with these CSV files typically edit them in
an IDE like PhpStorm directly and don't use Excel for
this anymore. The PhpStorm plugin "Rainbox CSV" also
helps by coloring these files and other alternatives
like libreoffice do not have this 'all rows must have
same number of colums' restriction.

The patch drops the script, the runTests.sh usage and
the CI calls. This has the additional advantage that
line breaks for single fields are now possible, which
will further improve handling and readability of field
values in upcoming patches.

Resolves: #97274
Related: #83943
Releases: main, 11.5, 10.4
Change-Id: I2b4c2afc98c8471bccae1afb15e055182b563ee7
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/74131
Tested-by: core-ci <typo3@b13.com>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
---
 Build/Scripts/checkIntegrityCsvFixtures.php   | 239 ------------------
 Build/Scripts/runTests.sh                     |  14 -
 Build/gitlab-ci/nightly/integrity.yml         |   1 -
 Build/gitlab-ci/pre-merge/integrity.yml       |   1 -
 Build/testing-docker/local/docker-compose.yml |  28 --
 5 files changed, 283 deletions(-)
 delete mode 100755 Build/Scripts/checkIntegrityCsvFixtures.php

diff --git a/Build/Scripts/checkIntegrityCsvFixtures.php b/Build/Scripts/checkIntegrityCsvFixtures.php
deleted file mode 100755
index 9bc94e64796e..000000000000
--- a/Build/Scripts/checkIntegrityCsvFixtures.php
+++ /dev/null
@@ -1,239 +0,0 @@
-#!/usr/bin/env php
-<?php
-declare(strict_types=1);
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use TYPO3\CMS\Core\Utility\MathUtility;
-
-require __DIR__ . '/../../vendor/autoload.php';
-
-if (PHP_SAPI !== 'cli') {
-    die('Script must be called from command line.' . chr(10));
-}
-
-/**
- * Core integrity test script:
- *
- * Find all CSV files in fixtures and make sure they have the correct column
- * count across all lines in them and fix them if --fix argument is given.
- */
-class checkIntegrityCsvFixtures
-{
-    /**
-     * @var bool True to fix broken files
-     */
-    private $fix = false;
-
-    /**
-     * @var bool True to drop superfluous comma on all CSV fixture files
-     */
-    private $fixAll = false;
-
-    public function setFix(bool $fix)
-    {
-        $this->fix = $fix;
-    }
-
-    public function setFixAll(bool $fixAll)
-    {
-        $this->fixAll = $fixAll;
-    }
-
-    /**
-     * Executes the CGL check.
-     * The return value is used directly in the ext() call outside this class.
-     *
-     * @return int
-     */
-    public function execute(): int
-    {
-        $filesToProcess = $this->findCsvFixtures();
-        $outputLines = [];
-        $output = new \Symfony\Component\Console\Output\ConsoleOutput();
-
-        $exitStatus = 0;
-        /** @var \SplFileInfo $csvFixture */
-        foreach ($filesToProcess as $csvFixture) {
-            $fullFilePath = $csvFixture->getRealPath();
-            if ($this->fixAll) {
-                $changed = $this->fixCsvFile($fullFilePath);
-                if ($changed) {
-                    $outputLines[] = 'Changed file "' . $this->formatOutputString($this->getRelativePath($fullFilePath)) . '"';
-                }
-                continue;
-            }
-            $singleFileScanResult = $this->validateCsvFile($fullFilePath);
-            if ($singleFileScanResult !== '') {
-                if ($this->fix) {
-                    $this->fixCsvFile($fullFilePath);
-                    $outputLines[] = 'Fixed file "' . $this->formatOutputString($this->getRelativePath($fullFilePath)) . '"';
-                } else {
-                    $exitStatus = 1;
-                    $outputLines[] = 'File "' . $this->formatOutputString($this->getRelativePath($fullFilePath)) . '"'
-                        . ' is not in valid CSV format: ' . $singleFileScanResult;
-                }
-            }
-        }
-        if (!empty($outputLines)) {
-            foreach ($outputLines as $line) {
-                $output->writeln($line);
-            }
-        }
-        return $exitStatus;
-    }
-
-    /**
-     * Finds all CSV fixtures in TYPO3s core
-     *
-     * @return \Symfony\Component\Finder\Finder
-     */
-    private function findCsvFixtures(): \Symfony\Component\Finder\Finder
-    {
-        $finder = new Symfony\Component\Finder\Finder();
-        $csvFixtures = $finder
-            ->files()
-            ->in(__DIR__ . '/../../typo3/sysext/*/Tests/Functional/**')
-            ->name('*.csv');
-        return $csvFixtures;
-    }
-
-    /**
-     * Checks if a CSV uses the same amount of columns across all
-     * lines in that file
-     *
-     * @param string $csvFixture
-     * @return string
-     */
-    private function validateCsvFile(string $csvFixture): string
-    {
-        // Load file content into array split by line
-        $lines = file($csvFixture);
-        $columnCount = 0;
-        foreach ($lines as $index => $line) {
-            // count columns in file
-            $columns = str_getcsv($line);
-            if ($columnCount === 0) {
-                $columnCount = count($columns);
-            } else {
-                if (count($columns) !== $columnCount) {
-                    // Skip CSV lines with starting with comments
-                    if (count($columns) === 1 && strpos($columns[0], '#') === 0) {
-                        continue;
-                    }
-                    return 'Line ' . ($index + 1) . '; Expected column count: ' . $columnCount . '; Actual: ' . count($columns);
-                }
-            }
-        }
-        return '';
-    }
-
-    /**
-     * Fix a single CSV file.
-     *
-     * @param string $csvFixture
-     * @return bool True if the file has been changed
-     */
-    private function fixCsvFile(string $csvFixture): bool
-    {
-        $changeNeeded = false;
-        // Load file content into array split by line
-        $lines = file($csvFixture);
-        $neededColumns = 0;
-        $csvLines = [];
-        foreach ($lines as $line) {
-            // Find out how many columns are needed in this file
-            $csvLine = str_getcsv($line);
-            $csvLines[] = $csvLine;
-            foreach ($csvLine as $columnNumber => $columnContent) {
-                if (!empty($columnContent) && $columnNumber + 1 > $neededColumns) {
-                    $neededColumns = $columnNumber + 1;
-                }
-            }
-        }
-        foreach ($csvLines as $csvLine) {
-            // Set $changeNeeded to true if this file needs an update and line is not a comment
-            if (count($csvLine) !== $neededColumns && substr($csvLine[0], 0, 2) !== '# ') {
-                $changeNeeded = true;
-                break;
-            }
-        }
-        if ($changeNeeded) {
-            // Update file
-            $fileHandle = fopen($csvFixture, 'w');
-            if (!$fileHandle) {
-                throw new \Exception('Opening file "' . $csvFixture . '" for writing failed.');
-            }
-            foreach ($csvLines as $csvLine) {
-                // Extend / reduce to needed size
-                $csvLine = array_slice(array_pad($csvLine, $neededColumns, ''), 0, $neededColumns);
-                $isComment = false;
-                $line = array_reduce($csvLine, function ($carry, $column) use (&$isComment) {
-                    if ($carry === null && substr($column, 0, 2) === '# ') {
-                        $isComment = true;
-                        $carry .= $column;
-                    } elseif ($isComment) {
-                        // comment lines are not filled up with comma
-                        return $carry;
-                    } elseif (empty($column) && $column !== '0') {
-                        // No leading comma if first column
-                        $carry .= $carry === null ? '' : ',';
-                    } elseif (MathUtility::canBeInterpretedAsInteger($column)) {
-                        // No leading comma if first column and integer payload
-                        $carry .= ($carry === null ? '' : ',') . $column;
-                    } else {
-                        // No leading comma if first column and string payload and quote " to ""
-                        $column = str_replace('"', '""', $column);
-                        $carry .= ($carry === null ? '' : ',') . '"' . $column . '"';
-                    }
-                    return $carry;
-                });
-                fwrite($fileHandle, $line . chr(10));
-            }
-            fclose($fileHandle);
-        }
-        return $changeNeeded;
-    }
-
-    private function getRelativePath(string $fullPath): string
-    {
-        $pathSegment = str_replace('Build/Scripts', '', __DIR__);
-        return str_replace($pathSegment, '', $fullPath);
-    }
-
-    /**
-     * Makes the output on CLI a bit more readable
-     *
-     * @param string $filename
-     * @return string
-     */
-    private function formatOutputString(string $filename): string
-    {
-        $pattern = '#typo3[\\\\/]sysext[\\\\/](?<extName>[a-z].+?)[\\\\/]Tests[\\\\/]Functional[\\\\/](?<file>.*)#';
-        preg_match_all($pattern, $filename, $matches, PREG_SET_ORDER, 0);
-        if (isset($matches[0])) {
-            return 'EXT:' . $matches[0]['extName'] . ' > ' . $matches[0]['file'];
-        }
-        return $filename;
-    }
-}
-
-$cglFixer = new checkIntegrityCsvFixtures();
-$args = getopt('', ['fix', 'fixAll']);
-if (array_key_exists('fix', $args)) {
-    $cglFixer->setFix(true);
-}
-if (array_key_exists('fixAll', $args)) {
-    $cglFixer->setFixAll(true);
-}
-exit($cglFixer->execute());
diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh
index ee7be39e5699..1fc57c9a2881 100755
--- a/Build/Scripts/runTests.sh
+++ b/Build/Scripts/runTests.sh
@@ -96,7 +96,6 @@ Options:
             - checkAnnotations: check php code for allowed annotations
             - checkBom: check UTF-8 files do not contain BOM
             - checkComposer: check composer.json files for version integrity
-            - checkCsvFixtures: test integrity of functional test csv fixtures
             - checkExceptionCodes: test core for duplicate exception codes
             - checkExtensionScannerRst: test all .rst files referenced by extension scanner exist
             - checkFilePathLength: test core file paths do not exceed maximum length
@@ -109,7 +108,6 @@ Options:
             - composerInstallMin: "composer update --prefer-lowest", with platform.php set to PHP version x.x.0.
             - composerValidate: "composer validate"
             - docBlockCheck: Scan php doc blocks for validity
-            - fixCsvFixtures: fix broken functional test csv fixtures
             - functional: functional tests
             - install: installation acceptance tests, only with -d mariadb|postgres|sqlite
             - lint: PHP linting
@@ -493,12 +491,6 @@ case ${TEST_SUITE} in
         SUITE_EXIT_CODE=$?
         docker-compose down
         ;;
-    checkCsvFixtures)
-        setUpDockerComposeDotEnv
-        docker-compose run check_csv_fixtures
-        SUITE_EXIT_CODE=$?
-        docker-compose down
-        ;;
     checkExceptionCodes)
         setUpDockerComposeDotEnv
         docker-compose run check_exception_codes
@@ -571,12 +563,6 @@ case ${TEST_SUITE} in
         SUITE_EXIT_CODE=$?
         docker-compose down
         ;;
-    fixCsvFixtures)
-        setUpDockerComposeDotEnv
-        docker-compose run fix_csv_fixtures
-        SUITE_EXIT_CODE=$?
-        docker-compose down
-        ;;
     functional)
         handleDbmsAndDriverOptions
         setUpDockerComposeDotEnv
diff --git a/Build/gitlab-ci/nightly/integrity.yml b/Build/gitlab-ci/nightly/integrity.yml
index a3fc42a8ac79..4116beec83cb 100644
--- a/Build/gitlab-ci/nightly/integrity.yml
+++ b/Build/gitlab-ci/nightly/integrity.yml
@@ -57,7 +57,6 @@ integration various php 7.2:
     - Build/Scripts/runTests.sh -s checkRst -p 7.2
     - Build/Scripts/runTests.sh -s checkFilePathLength -p 7.2
     - Build/Scripts/runTests.sh -s checkExtensionScannerRst -p 7.2
-    - Build/Scripts/runTests.sh -s checkCsvFixtures -p 7.2
     - Build/Scripts/runTests.sh -s checkBom -p 7.2
     - Build/Scripts/runTests.sh -s checkComposer -p 7.2
 
diff --git a/Build/gitlab-ci/pre-merge/integrity.yml b/Build/gitlab-ci/pre-merge/integrity.yml
index ab17bd84b347..eb77dcc1ec17 100644
--- a/Build/gitlab-ci/pre-merge/integrity.yml
+++ b/Build/gitlab-ci/pre-merge/integrity.yml
@@ -45,7 +45,6 @@ integration various php 7.2 pre-merge:
     - Build/Scripts/runTests.sh -s checkRst -p 7.2
     - Build/Scripts/runTests.sh -s checkFilePathLength -p 7.2
     - Build/Scripts/runTests.sh -s checkExtensionScannerRst -p 7.2
-    - Build/Scripts/runTests.sh -s checkCsvFixtures -p 7.2
     - Build/Scripts/runTests.sh -s checkBom -p 7.2
     - Build/Scripts/runTests.sh -s checkComposer -p 7.2
 
diff --git a/Build/testing-docker/local/docker-compose.yml b/Build/testing-docker/local/docker-compose.yml
index 2dc691b064e4..047e6ffe7292 100644
--- a/Build/testing-docker/local/docker-compose.yml
+++ b/Build/testing-docker/local/docker-compose.yml
@@ -514,34 +514,6 @@ services:
         php -dxdebug.mode=off Build/Scripts/checkIntegrityComposer.php;
       "
 
-  check_csv_fixtures:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    volumes:
-    - ${CORE_ROOT}:${CORE_ROOT}
-    working_dir: ${CORE_ROOT}
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        php -dxdebug.mode=off Build/Scripts/checkIntegrityCsvFixtures.php;
-      "
-
-  fix_csv_fixtures:
-    image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
-    user: "${HOST_UID}"
-    volumes:
-    - ${CORE_ROOT}:${CORE_ROOT}
-    working_dir: ${CORE_ROOT}
-    command: >
-      /bin/sh -c "
-        if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
-          set -x
-        fi
-        php -dxdebug.mode=off Build/Scripts/checkIntegrityCsvFixtures.php --fix;
-      "
-
   check_exception_codes:
     image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
     user: "${HOST_UID}"
-- 
GitLab