diff --git a/app/cdash/tests/CMakeLists.txt b/app/cdash/tests/CMakeLists.txt
index 353249464b..957d4ab9e9 100644
--- a/app/cdash/tests/CMakeLists.txt
+++ b/app/cdash/tests/CMakeLists.txt
@@ -596,5 +596,7 @@ set_tests_properties(misassignedconfigure PROPERTIES DEPENDS /Feature/SubProject
add_laravel_test(/Feature/RemoteWorkers)
set_tests_properties(/Feature/RemoteWorkers PROPERTIES DEPENDS install_3)
+add_laravel_test(/Unit/app/Console/Command/ValidateXmlCommandTest)
+
add_subdirectory(ctest)
add_subdirectory(autoremovebuilds)
diff --git a/tests/Unit/app/Console/Command/ValidateXmlCommandTest.php b/tests/Unit/app/Console/Command/ValidateXmlCommandTest.php
new file mode 100644
index 0000000000..e849d796ef
--- /dev/null
+++ b/tests/Unit/app/Console/Command/ValidateXmlCommandTest.php
@@ -0,0 +1,145 @@
+>
+ */
+ private function formatCommandParams()
+ {
+ $data_dir = base_path()."/tests/data/XmlValidation";
+ $file_paths = [];
+ foreach (func_get_args() as $file) {
+ $file_paths[] = "{$data_dir}/{$file}";
+ }
+ return [
+ 'xml_file' => $file_paths,
+ ];
+ }
+
+ /**
+ * Handle common assertions for a 'success' validation outcome
+ *
+ * @param string $output, int $rc, string $msg
+ * @return void
+ */
+ private function assertValidationSuccess(string $output, int $rc, string $msg): void
+ {
+ $this::assertMatchesRegularExpression("/All XML file checks passed./", $output, $msg);
+ $this::assertEquals(Command::SUCCESS, $rc, "Passing validation should return a SUCCESS return code");
+ }
+
+ /**
+ * Handle common assertions for a 'failure' validation outcome
+ *
+ * @param string $output, int $rc, string $msg, bool $skipped_files
+ * @return void
+ */
+ private function assertValidationFailure(string $output, int $rc, string $msg, bool $skipped_files=false): void
+ {
+ $output_regex = $skipped_files ? "/Some XML file checks were skipped!/" : "/Some XML file checks did not pass!/";
+ $this::assertMatchesRegularExpression($output_regex, $output, $msg);
+ $this::assertEquals(Command::FAILURE, $rc, "Failed validation should return a FAILURE return code");
+ }
+
+ /**
+ * Tests validating a single valid file
+ *
+ * @return void
+ */
+ public function testSingleValidXml(): void
+ {
+ $params = $this->formatCommandParams("valid_Build.xml");
+ $rc = Artisan::call('submission:validate', $params);
+ $output = trim(Artisan::output());
+ $this->assertValidationSuccess($output, $rc, "A single valid Build XML file should pass.");
+ }
+
+ /**
+ * Tests validating an invalid file path
+ *
+ * @return void
+ */
+ public function testInvalidFilePath(): void
+ {
+ $params = $this->formatCommandParams("no_such_file.xml");
+ $rc = Artisan::call('submission:validate', $params);
+ $output = trim(Artisan::output());
+ $this->assertValidationFailure($output, $rc, "An invalid file path should fail.", $skipped=true);
+ }
+
+ /**
+ * Tests validating a single file of unknown schema
+ *
+ * @return void
+ */
+ public function testUnknownSchema(): void
+ {
+ $params = $this->formatCommandParams("invalid_type.xml");
+ $rc = Artisan::call('submission:validate', $params);
+ $output = trim(Artisan::output());
+ $this->assertValidationFailure($output, $rc, "An XML file that doesn't match any supported schemas should fail.");
+ }
+
+ /**
+ * Tests validating a single file that does not adhere to its schema
+ *
+ * @return void
+ */
+ public function testSingleInvalidXml(): void
+ {
+ $params = $this->formatCommandParams("invalid_Configure.xml");
+ $rc = Artisan::call('submission:validate', $params);
+ $output = trim(Artisan::output());
+ $this->assertValidationFailure($output, $rc, "An XML file that doesn't adhere to its corresponding schema should fail.");
+ }
+
+ /**
+ * Tests validating a single file with invalid syntax
+ *
+ * @return void
+ */
+ public function testSingleInvalidSyntaxXml(): void
+ {
+ $params = $this->formatCommandParams("invalid_syntax_Build.xml");
+ $rc = Artisan::call('submission:validate', $params);
+ $output = trim(Artisan::output());
+ $this->assertValidationFailure($output, $rc, "An XML file with syntax errors should fail.");
+ }
+ /**
+ * Tests validating multiple valid files
+ *
+ * @return void
+ */
+ public function testMultipleValidXml(): void
+ {
+ $params = $this->formatCommandParams("valid_Build.xml", "valid_Test.xml");
+ $rc = Artisan::call('submission:validate', $params);
+ $output = trim(Artisan::output());
+ $this->assertValidationSuccess($output, $rc, "Multiple valid XML files should pass.");
+ }
+
+ /**
+ * Tests validating multiple files where some are invalid
+ *
+ * @return void
+ */
+ public function testMultipleFilesFailure(): void
+ {
+ $params = $this->formatCommandParams("valid_Build.xml", "invalid_Configure.xml", "valid_Test.xml");
+ $rc = Artisan::call('submission:validate', $params);
+ $output = trim(Artisan::output());
+ $this->assertValidationFailure($output, $rc, "Validation of a set of input files should fail when one or more errors occur.");
+ }
+}
diff --git a/tests/data/XmlValidation/invalid_Configure.xml b/tests/data/XmlValidation/invalid_Configure.xml
new file mode 100644
index 0000000000..e299bb8523
--- /dev/null
+++ b/tests/data/XmlValidation/invalid_Configure.xml
@@ -0,0 +1,12 @@
+
+
+
+ "cmake" "/foo/bar/src"
+
+ 0
+ 0
+
+
diff --git a/tests/data/XmlValidation/invalid_syntax_Build.xml b/tests/data/XmlValidation/invalid_syntax_Build.xml
new file mode 100644
index 0000000000..f564dad59a
--- /dev/null
+++ b/tests/data/XmlValidation/invalid_syntax_Build.xml
@@ -0,0 +1,35 @@
+
+
+
+ Aug 13 09:24 EDT
+ 1439472247
+ /opt/cmake/bin/cmake --build . --config "Debug" -- -i
+
+
+ foo
+ C++
+ foo
+ executable
+
+
+ /foo/bar/bin
+ /usr/local/bin/c++
+ CMakeFiles/foo.dir/foo.cxx.o
+ -o
+ foo
+
+
+
+ c++: error: CMakeFiles/foo.dir/foo.cxx.o: No such file or directory
+ 1
+
+
+
+ Sep 1 09:24 EDT
+ 0
+
+
diff --git a/tests/data/XmlValidation/invalid_type.xml b/tests/data/XmlValidation/invalid_type.xml
new file mode 100644
index 0000000000..6897922410
--- /dev/null
+++ b/tests/data/XmlValidation/invalid_type.xml
@@ -0,0 +1,9 @@
+
+
+
+ Hello World
+
+
diff --git a/tests/data/XmlValidation/valid_Build.xml b/tests/data/XmlValidation/valid_Build.xml
new file mode 100644
index 0000000000..acfb3b7cb1
--- /dev/null
+++ b/tests/data/XmlValidation/valid_Build.xml
@@ -0,0 +1,34 @@
+
+
+
+ Aug 13 09:24 EDT
+ 1439472247
+ /opt/cmake/bin/cmake --build . --config "Debug" -- -i
+
+
+ foo
+ C++
+ foo
+ executable
+
+
+ /foo/bar/bin
+ /usr/local/bin/c++
+ CMakeFiles/foo.dir/foo.cxx.o
+ -o
+ foo
+
+
+
+ c++: error: CMakeFiles/foo.dir/foo.cxx.o: No such file or directory
+ 1
+
+
+
+ Sep 1 09:24 EDT
+ 0
+
+
diff --git a/tests/data/XmlValidation/valid_Test.xml b/tests/data/XmlValidation/valid_Test.xml
new file mode 100644
index 0000000000..434c48c5d8
--- /dev/null
+++ b/tests/data/XmlValidation/valid_Test.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ ./foo
+
+
+ foo
+ .
+ ./foo
+ /tmp/bin/foo
+
+
+ Failed
+
+
+ 1
+
+
+
+ 0
+
+