diff --git a/src/Peachpied.PhpUnit.TestAdapter/EnvironmentHelper.cs b/src/Peachpied.PhpUnit.TestAdapter/EnvironmentHelper.cs
index 4608004..84cb314 100644
--- a/src/Peachpied.PhpUnit.TestAdapter/EnvironmentHelper.cs
+++ b/src/Peachpied.PhpUnit.TestAdapter/EnvironmentHelper.cs
@@ -34,5 +34,20 @@ namespace Peachpied.PhpUnit.TestAdapter
return assemblyDir;
}
}
+
+ ///
+ /// Generate a file name in the given directory with the given prefix which
+ /// does not exists in it yet.
+ ///
+ public static string GetNonexistingFilePath(string dir, string prefix = "")
+ {
+ string filePath;
+ do
+ {
+ filePath = Path.Combine(dir, prefix + Path.GetRandomFileName());
+ } while (File.Exists(filePath));
+
+ return filePath;
+ }
}
}
diff --git a/src/Peachpied.PhpUnit.TestAdapter/PhpUnitHelper.cs b/src/Peachpied.PhpUnit.TestAdapter/PhpUnitHelper.cs
index c2f9eb1..4a219fd 100644
--- a/src/Peachpied.PhpUnit.TestAdapter/PhpUnitHelper.cs
+++ b/src/Peachpied.PhpUnit.TestAdapter/PhpUnitHelper.cs
@@ -4,6 +4,7 @@ using PHPUnit.Framework;
using PHPUnit.TextUI;
using System;
using System.Collections.Generic;
+using System.IO;
using System.Reflection;
using System.Text;
@@ -14,6 +15,11 @@ namespace Peachpied.PhpUnit.TestAdapter
///
internal static class PhpUnitHelper
{
+ ///
+ /// Current PHPUnit version.
+ ///
+ public static Version Version { get; } = typeof(TestCase).Assembly.GetName().Version;
+
private const string PharName = "phpunit.phar";
static PhpUnitHelper()
@@ -22,10 +28,30 @@ namespace Peachpied.PhpUnit.TestAdapter
Context.AddScriptReference(typeof(TestCase).Assembly);
}
+ ///
+ /// Find a PHPUnit configuration file in the given directory, return null if not present.
+ ///
+ public static string TryFindConfigFile(string dir)
+ {
+ string primaryConfig = Path.Combine(dir, "phpunit.xml");
+ if (File.Exists(primaryConfig))
+ {
+ return primaryConfig;
+ }
+
+ string secondaryConfig = Path.Combine(dir, "phpunit.xml.dist");
+ if (File.Exists(secondaryConfig))
+ {
+ return secondaryConfig;
+ }
+
+ return null;
+ }
+
///
/// Run PHPUnit on the given assembly and command line arguments.
///
- public static void Launch(string cwd, string testedAssembly, string[] args, Action initCalblack = null, Action finishCallback = null)
+ public static void Launch(string cwd, string testedAssembly, string[] args, Action initCallback = null, Action finishCallback = null)
{
// Load assembly with tests (if not loaded yet)
Context.AddScriptReference(Assembly.LoadFrom(testedAssembly));
@@ -39,7 +65,7 @@ namespace Peachpied.PhpUnit.TestAdapter
ctx.Server[CommonPhpArrayKeys.SCRIPT_NAME] = "__DUMMY_INVALID_FILE";
// Perform any custom operations on the context
- initCalblack?.Invoke(ctx);
+ initCallback?.Invoke(ctx);
// Run the PHAR entry point so that all the classes are included
var pharLoader = Context.TryGetDeclaredScript(PharName);
diff --git a/src/Peachpied.PhpUnit.TestAdapter/PhpUnitTestExecutor.cs b/src/Peachpied.PhpUnit.TestAdapter/PhpUnitTestExecutor.cs
index a506774..1e825f3 100644
--- a/src/Peachpied.PhpUnit.TestAdapter/PhpUnitTestExecutor.cs
+++ b/src/Peachpied.PhpUnit.TestAdapter/PhpUnitTestExecutor.cs
@@ -1,11 +1,15 @@
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+using Pchp.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text;
using System.Text.RegularExpressions;
+using System.Xml;
+using System.Xml.Linq;
namespace Peachpied.PhpUnit.TestAdapter
{
@@ -44,8 +48,7 @@ namespace Peachpied.PhpUnit.TestAdapter
{
try
{
- // Inject our custom extension to report the test results
- var args = new[] { "--extensions", TestReporterExtension.PhpName };
+ var args = Array.Empty();
// Optionally filter the test cases by their names
if (testCases != null)
@@ -62,7 +65,8 @@ namespace Peachpied.PhpUnit.TestAdapter
string projectDir = EnvironmentHelper.TryFindProjectDirectory(Path.GetDirectoryName(source));
- PhpUnitHelper.Launch(projectDir, source, args,
+ // Inject our custom extension to report the test results
+ RunPhpUnitWithExtension(projectDir, source, args,
ctx =>
{
// Enable Peachpie to create an instance of our custom extension
@@ -79,6 +83,52 @@ namespace Peachpied.PhpUnit.TestAdapter
}
}
+ private void RunPhpUnitWithExtension(string projectDir, string source, string[] args, Action 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);
+ }
+ }
+ }
+ }
+
///
/// Cancel test execution, currently ignored.
///
diff --git a/src/Peachpied.PhpUnit.TestAdapter/XElementExtensions.cs b/src/Peachpied.PhpUnit.TestAdapter/XElementExtensions.cs
new file mode 100644
index 0000000..735f9c9
--- /dev/null
+++ b/src/Peachpied.PhpUnit.TestAdapter/XElementExtensions.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml.Linq;
+
+namespace Peachpied.PhpUnit.TestAdapter
+{
+ ///
+ /// LINQ to XML utility methods.
+ ///
+ internal static class XElementExtensions
+ {
+ ///
+ /// Retrieve the child element of the given name if it exists, or add an empty new one if it doesn't exist.
+ ///
+ public static XElement GetOrCreateElement(this XElement parent, XName elementName)
+ {
+ var element = parent.Element(elementName);
+ if (element == null)
+ {
+ element = new XElement(elementName);
+ parent.Add(element);
+ }
+
+ return element;
+ }
+ }
+}