private TestCase GetTestCase(string fullPhpTestName)
        {
            // "class::method with data set #0 ('foo', ...)" -> "class::method"
            int    dataSetStrPos = fullPhpTestName.IndexOf(" with data set ");
            string phpTestName   =
                (dataSetStrPos != -1)
                ? fullPhpTestName.Substring(0, dataSetStrPos)
                : fullPhpTestName;

            string vsTestName = PhpUnitHelper.GetTestNameFromPhp(phpTestName);

            return(new TestCase(vsTestName, PhpUnitTestExecutor.ExecutorUri, _testRunContext.Source)
            {
                DisplayName = fullPhpTestName   // Preserve the original data set information if present
            });
        }
        private void RunPhpUnitWithExtension(string projectDir, string source, string[] args, Action <Context> initCallback)
        {
            if (PhpUnitHelper.Version >= new Version(9, 1))
            {
                // The --extensions CLI argument is available from PHPUnit 9.1 (although it's documented only from 9.3)
                args = new[] { "--extensions", TestReporterExtension.PhpName }.Concat(args).ToArray();
                PhpUnitHelper.Launch(projectDir, source, args, initCallback);
            }
            else
            {
                // Older PHPUnit versions can receive extensions only in the configuration file,
                // so we need to create a modified version of the existing one and pass it to PHPUnit.
                // (or create one from scratch it if it doesn't exist at all)

                // Create PHPUnit configuration with the extension added
                string origConfigFile = PhpUnitHelper.TryFindConfigFile(projectDir);
                var    configXml      = (origConfigFile != null) ? XElement.Load(origConfigFile) : new XElement("phpunit");
                var    extensionsEl   = configXml.GetOrCreateElement("extensions");
                extensionsEl.Add(new XElement("extension", new XAttribute("class", TestReporterExtension.PhpName)));

                // Store the configuration in a temporary file to pass it to PHPUnit
                string tempConfigFile = null;
                try
                {
                    // The configuration file must be in the same folder as the project in order to work (to load PHP classes properly etc.)
                    tempConfigFile = EnvironmentHelper.GetNonexistingFilePath(projectDir, "phpunit.xml.");

                    // Save the config file without the BOM
                    using (var xmlWriter = new XmlTextWriter(tempConfigFile, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)))
                    {
                        configXml.Save(xmlWriter);
                    }

                    args = new[] { "--configuration", tempConfigFile }.Concat(args).ToArray();
                    PhpUnitHelper.Launch(projectDir, source, args, initCallback);
                }
                finally
                {
                    if (File.Exists(tempConfigFile))
                    {
                        File.Delete(tempConfigFile);
                    }
                }
            }
        }
        private static void ProcessSource(ITestCaseDiscoverySink discoverySink, string source)
        {
            string tempTestsXml = null;

            try
            {
                // Use XML output of PHPUnit to gather all the tests and report them
                string projectDir = EnvironmentHelper.TryFindProjectDirectory(Path.GetDirectoryName(source));
                tempTestsXml = Path.GetTempFileName();
                PhpUnitHelper.Launch(projectDir, source, new[] { "--list-tests-xml", tempTestsXml },
                                     finishCallback: ctx => ProcessTestsXml(ctx, source, tempTestsXml, discoverySink)
                                     );
            }
            finally
            {
                if (File.Exists(tempTestsXml))
                {
                    File.Delete(tempTestsXml);
                }
            }
        }
        private static void ProcessTestsXml(Pchp.Core.Context ctx, string source, string path, ITestCaseDiscoverySink discoverySink)
        {
            var testsEl = XElement.Load(path);

            foreach (var testCaseClassEl in testsEl.Descendants("testCaseClass"))
            {
                var phpClassName = testCaseClassEl.Attribute("name").Value;
                var classInfo    = ctx.GetDeclaredType(phpClassName, autoload: false);
                var filePath     = Path.GetFullPath(Path.Combine(ctx.RootPath, classInfo.RelativePath));

                // The file might not exist as PHPUnit may mistakenly select classes and methods in its own PHAR,
                // namely PHPUnit\Framework\WarningTestCase::Warning (happened with PHPUnit 7.5.9)
                var unit =
                    File.Exists(filePath)
                    ? CodeSourceUnit.ParseCode(File.ReadAllText(filePath), filePath)
                    : null;

                foreach (var testCaseMethodEl in testCaseClassEl.Descendants("testCaseMethod"))
                {
                    var methodName = testCaseMethodEl.Attribute("name").Value;

                    var testName = PhpUnitHelper.GetTestNameFromPhp(classInfo, methodName);
                    var testCase = new TestCase(testName, PhpUnitTestExecutor.ExecutorUri, source);

                    if (unit != null)
                    {
                        testCase.DisplayName  = $"{classInfo.Name}::{methodName}";
                        testCase.CodeFilePath = filePath;
                        testCase.LineNumber   = GetLineNumber(unit, phpClassName, methodName);
                    }
                    ;

                    ProcessTraits(testCase, testCaseMethodEl.Attribute("groups")?.Value);

                    discoverySink.SendTestCase(testCase);
                }
            }
        }
        private void RunSourceTest(string source, IFrameworkHandle frameworkHandle, IEnumerable <TestCase> testCases = null)
        {
            try
            {
                var args = Array.Empty <string>();

                // Optionally filter the test cases by their names
                if (testCases != null)
                {
                    var filterItems =
                        from testCase in testCases
                        let testName = PhpUnitHelper.GetPhpTestName(testCase.FullyQualifiedName)
                                       select $"^{Regex.Escape(testName)}(\\s|$)";

                    string filter = $"({string.Join("|", filterItems)})";

                    args = args.Concat(new[] { "--filter", filter }).ToArray();
                }

                string projectDir = EnvironmentHelper.TryFindProjectDirectory(Path.GetDirectoryName(source));

                // Inject our custom extension to report the test results
                RunPhpUnitWithExtension(projectDir, source, args,
                                        ctx =>
                {
                    // Enable Peachpie to create an instance of our custom extension
                    ctx.DeclareType <TestReporterExtension>();

                    // Pass data to the extension via Context
                    var testRunCtx = new TestRunContext(source, frameworkHandle);
                    ctx.SetProperty(testRunCtx);
                });
            }
            catch (System.Exception e)
            {
                frameworkHandle.SendMessage(TestMessageLevel.Error, e.Message + "\n" + e.StackTrace);
            }
        }