diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fdb205d5..5b597ce1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,322 +1,325 @@ 2, 'class' => array( 'ArcanistAliasWorkflow' => 'workflow/ArcanistAliasWorkflow.php', 'ArcanistAmendWorkflow' => 'workflow/ArcanistAmendWorkflow.php', 'ArcanistAnoidWorkflow' => 'workflow/ArcanistAnoidWorkflow.php', 'ArcanistApacheLicenseLinter' => 'lint/linter/ArcanistApacheLicenseLinter.php', 'ArcanistArcanistLinterTestCase' => 'lint/linter/__tests__/ArcanistArcanistLinterTestCase.php', 'ArcanistBackoutWorkflow' => 'workflow/ArcanistBackoutWorkflow.php', 'ArcanistBaseCommitParser' => 'parser/ArcanistBaseCommitParser.php', 'ArcanistBaseCommitParserTestCase' => 'parser/__tests__/ArcanistBaseCommitParserTestCase.php', 'ArcanistBaseTestResultParser' => 'unit/engine/ArcanistBaseTestResultParser.php', 'ArcanistBaseUnitTestEngine' => 'unit/engine/ArcanistBaseUnitTestEngine.php', 'ArcanistBaseWorkflow' => 'workflow/ArcanistBaseWorkflow.php', 'ArcanistBaseXHPASTLinter' => 'lint/linter/ArcanistBaseXHPASTLinter.php', 'ArcanistBookmarkWorkflow' => 'workflow/ArcanistBookmarkWorkflow.php', 'ArcanistBranchWorkflow' => 'workflow/ArcanistBranchWorkflow.php', 'ArcanistBritishTestCase' => 'configuration/__tests__/ArcanistBritishTestCase.php', 'ArcanistBrowseWorkflow' => 'workflow/ArcanistBrowseWorkflow.php', 'ArcanistBundle' => 'parser/ArcanistBundle.php', 'ArcanistBundleTestCase' => 'parser/__tests__/ArcanistBundleTestCase.php', 'ArcanistCSSLintLinter' => 'lint/linter/ArcanistCSSLintLinter.php', 'ArcanistCSSLintLinterTestCase' => 'lint/linter/__tests__/ArcanistCSSLintLinterTestCase.php', 'ArcanistCallConduitWorkflow' => 'workflow/ArcanistCallConduitWorkflow.php', 'ArcanistCapabilityNotSupportedException' => 'workflow/exception/ArcanistCapabilityNotSupportedException.php', 'ArcanistChooseInvalidRevisionException' => 'exception/ArcanistChooseInvalidRevisionException.php', 'ArcanistChooseNoRevisionsException' => 'exception/ArcanistChooseNoRevisionsException.php', 'ArcanistCloseRevisionWorkflow' => 'workflow/ArcanistCloseRevisionWorkflow.php', 'ArcanistCloseWorkflow' => 'workflow/ArcanistCloseWorkflow.php', 'ArcanistCommentRemover' => 'parser/ArcanistCommentRemover.php', 'ArcanistCommentRemoverTestCase' => 'parser/__tests__/ArcanistCommentRemoverTestCase.php', 'ArcanistCommitWorkflow' => 'workflow/ArcanistCommitWorkflow.php', 'ArcanistConduitLinter' => 'lint/linter/ArcanistConduitLinter.php', 'ArcanistConfiguration' => 'configuration/ArcanistConfiguration.php', 'ArcanistConfigurationDrivenLintEngine' => 'lint/engine/ArcanistConfigurationDrivenLintEngine.php', 'ArcanistCoverWorkflow' => 'workflow/ArcanistCoverWorkflow.php', 'ArcanistCppcheckLinter' => 'lint/linter/ArcanistCppcheckLinter.php', 'ArcanistCpplintLinter' => 'lint/linter/ArcanistCpplintLinter.php', 'ArcanistCpplintLinterTestCase' => 'lint/linter/__tests__/ArcanistCpplintLinterTestCase.php', 'ArcanistDiffChange' => 'parser/diff/ArcanistDiffChange.php', 'ArcanistDiffChangeType' => 'parser/diff/ArcanistDiffChangeType.php', 'ArcanistDiffHunk' => 'parser/diff/ArcanistDiffHunk.php', 'ArcanistDiffParser' => 'parser/ArcanistDiffParser.php', 'ArcanistDiffParserTestCase' => 'parser/__tests__/ArcanistDiffParserTestCase.php', 'ArcanistDiffUtils' => 'difference/ArcanistDiffUtils.php', 'ArcanistDiffUtilsTestCase' => 'difference/__tests__/ArcanistDiffUtilsTestCase.php', 'ArcanistDiffWorkflow' => 'workflow/ArcanistDiffWorkflow.php', 'ArcanistDifferentialCommitMessage' => 'differential/ArcanistDifferentialCommitMessage.php', 'ArcanistDifferentialCommitMessageParserException' => 'differential/ArcanistDifferentialCommitMessageParserException.php', 'ArcanistDifferentialRevisionHash' => 'differential/constants/ArcanistDifferentialRevisionHash.php', 'ArcanistDifferentialRevisionStatus' => 'differential/constants/ArcanistDifferentialRevisionStatus.php', 'ArcanistDownloadWorkflow' => 'workflow/ArcanistDownloadWorkflow.php', 'ArcanistEventType' => 'events/constant/ArcanistEventType.php', 'ArcanistExportWorkflow' => 'workflow/ArcanistExportWorkflow.php', 'ArcanistExternalLinter' => 'lint/linter/ArcanistExternalLinter.php', 'ArcanistFeatureWorkflow' => 'workflow/ArcanistFeatureWorkflow.php', 'ArcanistFilenameLinter' => 'lint/linter/ArcanistFilenameLinter.php', 'ArcanistFlagWorkflow' => 'workflow/ArcanistFlagWorkflow.php', 'ArcanistFlake8Linter' => 'lint/linter/ArcanistFlake8Linter.php', 'ArcanistFlake8LinterTestCase' => 'lint/linter/__tests__/ArcanistFlake8LinterTestCase.php', 'ArcanistFutureLinter' => 'lint/linter/ArcanistFutureLinter.php', 'ArcanistGeneratedLinter' => 'lint/linter/ArcanistGeneratedLinter.php', 'ArcanistGetConfigWorkflow' => 'workflow/ArcanistGetConfigWorkflow.php', 'ArcanistGitAPI' => 'repository/api/ArcanistGitAPI.php', 'ArcanistGitHookPreReceiveWorkflow' => 'workflow/ArcanistGitHookPreReceiveWorkflow.php', 'ArcanistHelpWorkflow' => 'workflow/ArcanistHelpWorkflow.php', 'ArcanistHgClientChannel' => 'hgdaemon/ArcanistHgClientChannel.php', 'ArcanistHgProxyClient' => 'hgdaemon/ArcanistHgProxyClient.php', 'ArcanistHgProxyServer' => 'hgdaemon/ArcanistHgProxyServer.php', 'ArcanistHgServerChannel' => 'hgdaemon/ArcanistHgServerChannel.php', 'ArcanistHookAPI' => 'repository/hookapi/ArcanistHookAPI.php', 'ArcanistInlinesWorkflow' => 'workflow/ArcanistInlinesWorkflow.php', 'ArcanistInstallCertificateWorkflow' => 'workflow/ArcanistInstallCertificateWorkflow.php', 'ArcanistJSHintLinter' => 'lint/linter/ArcanistJSHintLinter.php', 'ArcanistLandWorkflow' => 'workflow/ArcanistLandWorkflow.php', 'ArcanistLiberateWorkflow' => 'workflow/ArcanistLiberateWorkflow.php', 'ArcanistLicenseLinter' => 'lint/linter/ArcanistLicenseLinter.php', 'ArcanistLintConsoleRenderer' => 'lint/renderer/ArcanistLintConsoleRenderer.php', 'ArcanistLintEngine' => 'lint/engine/ArcanistLintEngine.php', 'ArcanistLintJSONRenderer' => 'lint/renderer/ArcanistLintJSONRenderer.php', 'ArcanistLintLikeCompilerRenderer' => 'lint/renderer/ArcanistLintLikeCompilerRenderer.php', 'ArcanistLintMessage' => 'lint/ArcanistLintMessage.php', 'ArcanistLintNoneRenderer' => 'lint/renderer/ArcanistLintNoneRenderer.php', 'ArcanistLintPatcher' => 'lint/ArcanistLintPatcher.php', 'ArcanistLintRenderer' => 'lint/renderer/ArcanistLintRenderer.php', 'ArcanistLintResult' => 'lint/ArcanistLintResult.php', 'ArcanistLintSeverity' => 'lint/ArcanistLintSeverity.php', 'ArcanistLintSummaryRenderer' => 'lint/renderer/ArcanistLintSummaryRenderer.php', 'ArcanistLintWorkflow' => 'workflow/ArcanistLintWorkflow.php', 'ArcanistLinter' => 'lint/linter/ArcanistLinter.php', 'ArcanistLinterTestCase' => 'lint/linter/__tests__/ArcanistLinterTestCase.php', 'ArcanistListWorkflow' => 'workflow/ArcanistListWorkflow.php', 'ArcanistMarkCommittedWorkflow' => 'workflow/ArcanistMarkCommittedWorkflow.php', 'ArcanistMercurialAPI' => 'repository/api/ArcanistMercurialAPI.php', 'ArcanistMercurialParser' => 'repository/parser/ArcanistMercurialParser.php', 'ArcanistMercurialParserTestCase' => 'repository/parser/__tests__/ArcanistMercurialParserTestCase.php', 'ArcanistMergeConflictLinter' => 'lint/linter/ArcanistMergeConflictLinter.php', 'ArcanistNoEffectException' => 'exception/usage/ArcanistNoEffectException.php', 'ArcanistNoEngineException' => 'exception/usage/ArcanistNoEngineException.php', 'ArcanistNoLintLinter' => 'lint/linter/ArcanistNoLintLinter.php', 'ArcanistNoLintTestCaseMisnamed' => 'lint/linter/__tests__/ArcanistNoLintTestCase.php', 'ArcanistPEP8Linter' => 'lint/linter/ArcanistPEP8Linter.php', 'ArcanistPEP8LinterTestCase' => 'lint/linter/__tests__/ArcanistPEP8LinterTestCase.php', 'ArcanistPHPCSLinterTestCase' => 'lint/linter/__tests__/ArcanistPHPCSLinterTestCase.php', 'ArcanistPasteWorkflow' => 'workflow/ArcanistPasteWorkflow.php', 'ArcanistPatchWorkflow' => 'workflow/ArcanistPatchWorkflow.php', 'ArcanistPhpcsLinter' => 'lint/linter/ArcanistPhpcsLinter.php', 'ArcanistPhutilLibraryLinter' => 'lint/linter/ArcanistPhutilLibraryLinter.php', 'ArcanistPhutilTestCase' => 'unit/engine/phutil/ArcanistPhutilTestCase.php', 'ArcanistPhutilTestCaseTestCase' => 'unit/engine/phutil/testcase/ArcanistPhutilTestCaseTestCase.php', 'ArcanistPhutilTestSkippedException' => 'unit/engine/phutil/testcase/ArcanistPhutilTestSkippedException.php', 'ArcanistPhutilTestTerminatedException' => 'unit/engine/phutil/testcase/ArcanistPhutilTestTerminatedException.php', 'ArcanistPhutilXHPASTLinter' => 'lint/linter/ArcanistPhutilXHPASTLinter.php', 'ArcanistPhutilXHPASTLinterTestCase' => 'lint/linter/__tests__/ArcanistPhutilXHPASTLinterTestCase.php', 'ArcanistPyFlakesLinter' => 'lint/linter/ArcanistPyFlakesLinter.php', 'ArcanistPyLintLinter' => 'lint/linter/ArcanistPyLintLinter.php', 'ArcanistRepositoryAPI' => 'repository/api/ArcanistRepositoryAPI.php', 'ArcanistRepositoryAPIMiscTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIMiscTestCase.php', 'ArcanistRepositoryAPIStateTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php', 'ArcanistRevertWorkflow' => 'workflow/ArcanistRevertWorkflow.php', 'ArcanistRubyLinter' => 'lint/linter/ArcanistRubyLinter.php', 'ArcanistRubyLinterTestCase' => 'lint/linter/__tests__/ArcanistRubyLinterTestCase.php', 'ArcanistScalaSBTLinter' => 'lint/linter/ArcanistScalaSBTLinter.php', 'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php', 'ArcanistSetConfigWorkflow' => 'workflow/ArcanistSetConfigWorkflow.php', 'ArcanistSettings' => 'configuration/ArcanistSettings.php', 'ArcanistShellCompleteWorkflow' => 'workflow/ArcanistShellCompleteWorkflow.php', 'ArcanistSingleLintEngine' => 'lint/engine/ArcanistSingleLintEngine.php', 'ArcanistSpellingDefaultData' => 'lint/linter/spelling/ArcanistSpellingDefaultData.php', 'ArcanistSpellingLinter' => 'lint/linter/ArcanistSpellingLinter.php', 'ArcanistSpellingLinterTestCase' => 'lint/linter/__tests__/ArcanistSpellingLinterTestCase.php', 'ArcanistSubversionAPI' => 'repository/api/ArcanistSubversionAPI.php', 'ArcanistSubversionHookAPI' => 'repository/hookapi/ArcanistSubversionHookAPI.php', 'ArcanistSvnHookPreCommitWorkflow' => 'workflow/ArcanistSvnHookPreCommitWorkflow.php', 'ArcanistTasksWorkflow' => 'workflow/ArcanistTasksWorkflow.php', 'ArcanistTestCase' => 'infrastructure/testing/ArcanistTestCase.php', 'ArcanistTextLinter' => 'lint/linter/ArcanistTextLinter.php', 'ArcanistTextLinterTestCase' => 'lint/linter/__tests__/ArcanistTextLinterTestCase.php', 'ArcanistTodoWorkflow' => 'workflow/ArcanistTodoWorkflow.php', 'ArcanistUncommittedChangesException' => 'exception/usage/ArcanistUncommittedChangesException.php', 'ArcanistUnitConsoleRenderer' => 'unit/renderer/ArcanistUnitConsoleRenderer.php', 'ArcanistUnitRenderer' => 'unit/renderer/ArcanistUnitRenderer.php', 'ArcanistUnitTestResult' => 'unit/ArcanistUnitTestResult.php', 'ArcanistUnitWorkflow' => 'workflow/ArcanistUnitWorkflow.php', 'ArcanistUpgradeWorkflow' => 'workflow/ArcanistUpgradeWorkflow.php', 'ArcanistUploadWorkflow' => 'workflow/ArcanistUploadWorkflow.php', 'ArcanistUsageException' => 'exception/ArcanistUsageException.php', 'ArcanistUserAbortException' => 'exception/usage/ArcanistUserAbortException.php', 'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php', 'ArcanistWorkingCopyIdentity' => 'workingcopyidentity/ArcanistWorkingCopyIdentity.php', 'ArcanistXHPASTLintNamingHook' => 'lint/linter/xhpast/ArcanistXHPASTLintNamingHook.php', 'ArcanistXHPASTLintNamingHookTestCase' => 'lint/linter/xhpast/__tests__/ArcanistXHPASTLintNamingHookTestCase.php', 'ArcanistXHPASTLintSwitchHook' => 'lint/linter/xhpast/ArcanistXHPASTLintSwitchHook.php', 'ArcanistXHPASTLintTestSwitchHook' => 'lint/linter/__tests__/ArcanistXHPASTLintTestSwitchHook.php', 'ArcanistXHPASTLinter' => 'lint/linter/ArcanistXHPASTLinter.php', 'ArcanistXHPASTLinterTestCase' => 'lint/linter/__tests__/ArcanistXHPASTLinterTestCase.php', + 'ArcanistXUnitTestResultParser' => 'unit/engine/ArcanistXUnitTestResultParser.php', 'CSharpToolsTestEngine' => 'unit/engine/CSharpToolsTestEngine.php', 'ComprehensiveLintEngine' => 'lint/engine/ComprehensiveLintEngine.php', 'ExampleLintEngine' => 'lint/engine/ExampleLintEngine.php', 'GoTestResultParser' => 'unit/engine/GoTestResultParser.php', 'GoTestResultParserTestCase' => 'unit/engine/__tests__/GoTestResultParserTestCase.php', 'NoseTestEngine' => 'unit/engine/NoseTestEngine.php', 'PHPUnitTestEngineTestCase' => 'unit/engine/__tests__/PHPUnitTestEngineTestCase.php', 'PhpunitResultParser' => 'unit/engine/PhpunitResultParser.php', 'PhpunitTestEngine' => 'unit/engine/PhpunitTestEngine.php', 'PhutilLintEngine' => 'lint/engine/PhutilLintEngine.php', 'PhutilUnitTestEngine' => 'unit/engine/PhutilUnitTestEngine.php', 'PhutilUnitTestEngineTestCase' => 'unit/engine/__tests__/PhutilUnitTestEngineTestCase.php', 'PytestTestEngine' => 'unit/engine/PytestTestEngine.php', 'UnitTestableArcanistLintEngine' => 'lint/engine/UnitTestableArcanistLintEngine.php', 'XUnitTestEngine' => 'unit/engine/XUnitTestEngine.php', + 'XUnitTestResultParserTestCase' => 'unit/engine/__tests__/XUnitTestResultParserTestCase.php', ), 'function' => array( ), 'xmap' => array( 'ArcanistAliasWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistAmendWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistAnoidWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistApacheLicenseLinter' => 'ArcanistLicenseLinter', 'ArcanistArcanistLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistBackoutWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistBaseCommitParserTestCase' => 'ArcanistTestCase', 'ArcanistBaseWorkflow' => 'Phobject', 'ArcanistBaseXHPASTLinter' => 'ArcanistFutureLinter', 'ArcanistBookmarkWorkflow' => 'ArcanistFeatureWorkflow', 'ArcanistBranchWorkflow' => 'ArcanistFeatureWorkflow', 'ArcanistBritishTestCase' => 'ArcanistTestCase', 'ArcanistBrowseWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistBundleTestCase' => 'ArcanistTestCase', 'ArcanistCSSLintLinter' => 'ArcanistExternalLinter', 'ArcanistCSSLintLinterTestCase' => 'ArcanistArcanistLinterTestCase', 'ArcanistCallConduitWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistCapabilityNotSupportedException' => 'Exception', 'ArcanistChooseInvalidRevisionException' => 'Exception', 'ArcanistChooseNoRevisionsException' => 'Exception', 'ArcanistCloseRevisionWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistCloseWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistCommentRemoverTestCase' => 'ArcanistTestCase', 'ArcanistCommitWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistConduitLinter' => 'ArcanistLinter', 'ArcanistConfigurationDrivenLintEngine' => 'ArcanistLintEngine', 'ArcanistCoverWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistCppcheckLinter' => 'ArcanistLinter', 'ArcanistCpplintLinter' => 'ArcanistLinter', 'ArcanistCpplintLinterTestCase' => 'ArcanistArcanistLinterTestCase', 'ArcanistDiffParserTestCase' => 'ArcanistTestCase', 'ArcanistDiffUtilsTestCase' => 'ArcanistTestCase', 'ArcanistDiffWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistDifferentialCommitMessageParserException' => 'Exception', 'ArcanistDownloadWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistEventType' => 'PhutilEventType', 'ArcanistExportWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistExternalLinter' => 'ArcanistFutureLinter', 'ArcanistFeatureWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistFilenameLinter' => 'ArcanistLinter', 'ArcanistFlagWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistFlake8Linter' => 'ArcanistExternalLinter', 'ArcanistFlake8LinterTestCase' => 'ArcanistArcanistLinterTestCase', 'ArcanistFutureLinter' => 'ArcanistLinter', 'ArcanistGeneratedLinter' => 'ArcanistLinter', 'ArcanistGetConfigWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistGitAPI' => 'ArcanistRepositoryAPI', 'ArcanistGitHookPreReceiveWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistHelpWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistHgClientChannel' => 'PhutilProtocolChannel', 'ArcanistHgServerChannel' => 'PhutilProtocolChannel', 'ArcanistInlinesWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistInstallCertificateWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistJSHintLinter' => 'ArcanistLinter', 'ArcanistLandWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistLiberateWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistLicenseLinter' => 'ArcanistLinter', 'ArcanistLintConsoleRenderer' => 'ArcanistLintRenderer', 'ArcanistLintJSONRenderer' => 'ArcanistLintRenderer', 'ArcanistLintLikeCompilerRenderer' => 'ArcanistLintRenderer', 'ArcanistLintNoneRenderer' => 'ArcanistLintRenderer', 'ArcanistLintSummaryRenderer' => 'ArcanistLintRenderer', 'ArcanistLintWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistLinterTestCase' => 'ArcanistPhutilTestCase', 'ArcanistListWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistMarkCommittedWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI', 'ArcanistMercurialParserTestCase' => 'ArcanistTestCase', 'ArcanistMergeConflictLinter' => 'ArcanistLinter', 'ArcanistNoEffectException' => 'ArcanistUsageException', 'ArcanistNoEngineException' => 'ArcanistUsageException', 'ArcanistNoLintLinter' => 'ArcanistLinter', 'ArcanistNoLintTestCaseMisnamed' => 'ArcanistLinterTestCase', 'ArcanistPEP8Linter' => 'ArcanistExternalLinter', 'ArcanistPEP8LinterTestCase' => 'ArcanistArcanistLinterTestCase', 'ArcanistPHPCSLinterTestCase' => 'ArcanistArcanistLinterTestCase', 'ArcanistPasteWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistPatchWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistPhpcsLinter' => 'ArcanistExternalLinter', 'ArcanistPhutilLibraryLinter' => 'ArcanistLinter', 'ArcanistPhutilTestCaseTestCase' => 'ArcanistPhutilTestCase', 'ArcanistPhutilTestSkippedException' => 'Exception', 'ArcanistPhutilTestTerminatedException' => 'Exception', 'ArcanistPhutilXHPASTLinter' => 'ArcanistBaseXHPASTLinter', 'ArcanistPhutilXHPASTLinterTestCase' => 'ArcanistArcanistLinterTestCase', 'ArcanistPyFlakesLinter' => 'ArcanistLinter', 'ArcanistPyLintLinter' => 'ArcanistLinter', 'ArcanistRepositoryAPIMiscTestCase' => 'ArcanistTestCase', 'ArcanistRepositoryAPIStateTestCase' => 'ArcanistTestCase', 'ArcanistRevertWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistRubyLinter' => 'ArcanistExternalLinter', 'ArcanistRubyLinterTestCase' => 'ArcanistArcanistLinterTestCase', 'ArcanistScalaSBTLinter' => 'ArcanistLinter', 'ArcanistScriptAndRegexLinter' => 'ArcanistLinter', 'ArcanistSetConfigWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistShellCompleteWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistSingleLintEngine' => 'ArcanistLintEngine', 'ArcanistSpellingLinter' => 'ArcanistLinter', 'ArcanistSpellingLinterTestCase' => 'ArcanistArcanistLinterTestCase', 'ArcanistSubversionAPI' => 'ArcanistRepositoryAPI', 'ArcanistSubversionHookAPI' => 'ArcanistHookAPI', 'ArcanistSvnHookPreCommitWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistTasksWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistTestCase' => 'ArcanistPhutilTestCase', 'ArcanistTextLinter' => 'ArcanistLinter', 'ArcanistTextLinterTestCase' => 'ArcanistArcanistLinterTestCase', 'ArcanistTodoWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistUncommittedChangesException' => 'ArcanistUsageException', 'ArcanistUnitConsoleRenderer' => 'ArcanistUnitRenderer', 'ArcanistUnitWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistUpgradeWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistUploadWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistUsageException' => 'Exception', 'ArcanistUserAbortException' => 'ArcanistUsageException', 'ArcanistWhichWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistXHPASTLintNamingHookTestCase' => 'ArcanistTestCase', 'ArcanistXHPASTLintTestSwitchHook' => 'ArcanistXHPASTLintSwitchHook', 'ArcanistXHPASTLinter' => 'ArcanistBaseXHPASTLinter', 'ArcanistXHPASTLinterTestCase' => 'ArcanistArcanistLinterTestCase', 'CSharpToolsTestEngine' => 'XUnitTestEngine', 'ComprehensiveLintEngine' => 'ArcanistLintEngine', 'ExampleLintEngine' => 'ArcanistLintEngine', 'GoTestResultParser' => 'ArcanistBaseTestResultParser', 'GoTestResultParserTestCase' => 'ArcanistTestCase', 'NoseTestEngine' => 'ArcanistBaseUnitTestEngine', 'PHPUnitTestEngineTestCase' => 'ArcanistTestCase', 'PhpunitResultParser' => 'ArcanistBaseTestResultParser', 'PhpunitTestEngine' => 'ArcanistBaseUnitTestEngine', 'PhutilLintEngine' => 'ArcanistLintEngine', 'PhutilUnitTestEngine' => 'ArcanistBaseUnitTestEngine', 'PhutilUnitTestEngineTestCase' => 'ArcanistTestCase', 'PytestTestEngine' => 'ArcanistBaseUnitTestEngine', 'UnitTestableArcanistLintEngine' => 'ArcanistLintEngine', 'XUnitTestEngine' => 'ArcanistBaseUnitTestEngine', + 'XUnitTestResultParserTestCase' => 'ArcanistTestCase', ), )); diff --git a/src/unit/engine/PytestTestEngine.php b/src/unit/engine/ArcanistXUnitTestResultParser.php similarity index 73% copy from src/unit/engine/PytestTestEngine.php copy to src/unit/engine/ArcanistXUnitTestResultParser.php index 37a43b72..a3fdacf8 100644 --- a/src/unit/engine/PytestTestEngine.php +++ b/src/unit/engine/ArcanistXUnitTestResultParser.php @@ -1,89 +1,97 @@ resolvex(); - - return $this->parseTestResults($junit_tmp); - } +final class ArcanistXUnitTestResultParser { + + /** + * Parse test results from provided input and return an array + * of ArcanistUnitTestResult + * + * @param string $path Path to test (Ignored) + * @param string $test_results String containing test results + * + * @return array ArcanistUnitTestResult + */ + public function parseTestResults($test_results) { + if (!strlen($test_results)) { + throw new Exception( + 'test_results argument to parseTestResults must not be empty'); + } - public function parseTestResults($junit_tmp) { // xunit xsd: https://gist.github.com/959290 $xunit_dom = new DOMDocument(); - $xunit_dom->loadXML(Filesystem::readFile($junit_tmp)); + $load_success = @$xunit_dom->loadXML($test_results); + + if (!$load_success) { + $input_start = phutil_utf8_shorten($test_results, 150); + throw new Exception( + "Failed to load XUnit report; Input starts with:\n\n {$input_start}"); + } $results = array(); $testcases = $xunit_dom->getElementsByTagName("testcase"); foreach ($testcases as $testcase) { $classname = $testcase->getAttribute("classname"); $name = $testcase->getAttribute("name"); $time = $testcase->getAttribute("time"); $status = ArcanistUnitTestResult::RESULT_PASS; $user_data = ""; // A skipped test is a test which was ignored using framework // mechanizms (e.g. @skip decorator) $skipped = $testcase->getElementsByTagName("skipped"); if ($skipped->length > 0) { $status = ArcanistUnitTestResult::RESULT_SKIP; $messages = array(); for ($ii = 0; $ii < $skipped->length; $ii++) { $messages[] = trim($skipped->item($ii)->nodeValue, " \n"); } $user_data .= implode("\n", $messages); } // Failure is a test which the code has explicitly failed by using // the mechanizms for that purpose. e.g., via an assertEquals $failures = $testcase->getElementsByTagName("failure"); if ($failures->length > 0) { $status = ArcanistUnitTestResult::RESULT_FAIL; $messages = array(); for ($ii = 0; $ii < $failures->length; $ii++) { $messages[] = trim($failures->item($ii)->nodeValue, " \n"); } $user_data .= implode("\n", $messages)."\n"; } // An errored test is one that had an unanticipated problem. e.g., an // unchecked throwable, or a problem with an implementation of the // test. $errors = $testcase->getElementsByTagName("error"); if ($errors->length > 0) { $status = ArcanistUnitTestResult::RESULT_BROKEN; $messages = array(); for ($ii = 0; $ii < $errors->length; $ii++) { $messages[] = trim($errors->item($ii)->nodeValue, " \n"); } $user_data .= implode("\n", $messages)."\n"; } $result = new ArcanistUnitTestResult(); $result->setName($classname.".".$name); $result->setResult($status); $result->setDuration($time); $result->setUserData($user_data); $results[] = $result; } return $results; } } diff --git a/src/unit/engine/NoseTestEngine.php b/src/unit/engine/NoseTestEngine.php index 49f03994..00296a08 100644 --- a/src/unit/engine/NoseTestEngine.php +++ b/src/unit/engine/NoseTestEngine.php @@ -1,244 +1,186 @@ getPaths(); $affected_tests = array(); foreach ($paths as $path) { $absolute_path = Filesystem::resolvePath($path); if (is_dir($absolute_path)) { $absolute_test_path = Filesystem::resolvePath("tests/".$path); if (is_readable($absolute_test_path)) { $affected_tests[] = $absolute_test_path; } } if (is_readable($absolute_path)) { $filename = basename($path); $directory = dirname($path); // assumes directory layout: tests//test_.py $relative_test_path = "tests/".$directory."/test_".$filename; $absolute_test_path = Filesystem::resolvePath($relative_test_path); if (is_readable($absolute_test_path)) { $affected_tests[] = $absolute_test_path; } } } return $this->runTests($affected_tests, './'); } public function runTests($test_paths, $source_path) { if (empty($test_paths)) { return array(); } $futures = array(); $tmpfiles = array(); foreach ($test_paths as $test_path) { $xunit_tmp = new TempFile(); $cover_tmp = new TempFile(); $future = $this->buildTestFuture($test_path, $xunit_tmp, $cover_tmp); $futures[$test_path] = $future; $tmpfiles[$test_path] = array( 'xunit' => $xunit_tmp, 'cover' => $cover_tmp, ); } $results = array(); foreach (Futures($futures)->limit(4) as $test_path => $future) { try { list($stdout, $stderr) = $future->resolvex(); } catch(CommandException $exc) { if ($exc->getError() > 1) { // 'nose' returns 1 when tests are failing/broken. throw $exc; } } $xunit_tmp = $tmpfiles[$test_path]['xunit']; $cover_tmp = $tmpfiles[$test_path]['cover']; + $this->parser = new ArcanistXUnitTestResultParser(); $results[] = $this->parseTestResults($source_path, $xunit_tmp, $cover_tmp); } return array_mergev($results); } public function buildTestFuture($path, $xunit_tmp, $cover_tmp) { $cmd_line = csprintf("nosetests --with-xunit --xunit-file=%s", $xunit_tmp); if ($this->getEnableCoverage() !== false) { $cmd_line .= csprintf(" --with-coverage --cover-xml " . "--cover-xml-file=%s", $cover_tmp); } return new ExecFuture("%C %s", $cmd_line, $path); } public function parseTestResults($source_path, $xunit_tmp, $cover_tmp) { - // xunit xsd: https://gist.github.com/959290 - $xunit_dom = new DOMDocument(); - $xunit_dom->loadXML(Filesystem::readFile($xunit_tmp)); + $results = $this->parser->parseTestResults( + Filesystem::readFile($xunit_tmp)); // coverage is for all testcases in the executed $path - $coverage = array(); if ($this->getEnableCoverage() !== false) { $coverage = $this->readCoverage($cover_tmp, $source_path); - } - - $results = array(); - $testcases = $xunit_dom->getElementsByTagName("testcase"); - foreach ($testcases as $testcase) { - $classname = $testcase->getAttribute("classname"); - $name = $testcase->getAttribute("name"); - $time = $testcase->getAttribute("time"); - - $status = ArcanistUnitTestResult::RESULT_PASS; - $user_data = ""; - - // A skipped test is a test which was ignored using framework - // mechanizms (e.g. @skip decorator) - $skipped = $testcase->getElementsByTagName("skipped"); - if ($skipped->length > 0) { - $status = ArcanistUnitTestResult::RESULT_SKIP; - $messages = array(); - for ($ii = 0; $ii < $skipped->length; $ii++) { - $messages[] = trim($skipped->item($ii)->nodeValue, " \n"); - } - - $user_data .= implode("\n", $messages); + foreach ($results as $result) { + $result->setCoverage($coverage); } - - // Failure is a test which the code has explicitly failed by using - // the mechanizms for that purpose. e.g., via an assertEquals - $failures = $testcase->getElementsByTagName("failure"); - if ($failures->length > 0) { - $status = ArcanistUnitTestResult::RESULT_FAIL; - $messages = array(); - for ($ii = 0; $ii < $failures->length; $ii++) { - $messages[] = trim($failures->item($ii)->nodeValue, " \n"); - } - - $user_data .= implode("\n", $messages)."\n"; - } - - // An errored test is one that had an unanticipated problem. e.g., an - // unchecked throwable, or a problem with an implementation of the - // test. - $errors = $testcase->getElementsByTagName("error"); - if ($errors->length > 0) { - $status = ArcanistUnitTestResult::RESULT_BROKEN; - $messages = array(); - for ($ii = 0; $ii < $errors->length; $ii++) { - $messages[] = trim($errors->item($ii)->nodeValue, " \n"); - } - - $user_data .= implode("\n", $messages)."\n"; - } - - $result = new ArcanistUnitTestResult(); - $result->setName($classname.".".$name); - $result->setResult($status); - $result->setDuration($time); - $result->setCoverage($coverage); - $result->setUserData($user_data); - - $results[] = $result; } return $results; } public function readCoverage($cover_file, $source_path) { $coverage_dom = new DOMDocument(); $coverage_dom->loadXML(Filesystem::readFile($cover_file)); $reports = array(); $classes = $coverage_dom->getElementsByTagName("class"); foreach ($classes as $class) { // filename is actually python module path with ".py" at the end, // e.g.: tornado.web.py $relative_path = explode(".", $class->getAttribute("filename")); array_pop($relative_path); $relative_path = $source_path .'/'. implode("/", $relative_path); // first we check if the path is a directory (a Python package), if it is // set relative and absolute paths to have __init__.py at the end. $absolute_path = Filesystem::resolvePath($relative_path); if (is_dir($absolute_path)) { $relative_path .= "/__init__.py"; $absolute_path .= "/__init__.py"; } // then we check if the path with ".py" at the end is file (a Python // submodule), if it is - set relative and absolute paths to have // ".py" at the end. if (is_file($absolute_path.".py")) { $relative_path .= ".py"; $absolute_path .= ".py"; } if (!file_exists($absolute_path)) { continue; } // get total line count in file $line_count = count(file($absolute_path)); $coverage = ""; $start_line = 1; $lines = $class->getElementsByTagName("line"); for ($ii = 0; $ii < $lines->length; $ii++) { $line = $lines->item($ii); $next_line = intval($line->getAttribute("number")); for ($start_line; $start_line < $next_line; $start_line++) { $coverage .= "N"; } if (intval($line->getAttribute("hits")) == 0) { $coverage .= "U"; } else if (intval($line->getAttribute("hits")) > 0) { $coverage .= "C"; } $start_line++; } if ($start_line < $line_count) { foreach (range($start_line, $line_count) as $line_num) { $coverage .= "N"; } } $reports[$relative_path] = $coverage; } return $reports; } } diff --git a/src/unit/engine/PytestTestEngine.php b/src/unit/engine/PytestTestEngine.php index 37a43b72..ca2da944 100644 --- a/src/unit/engine/PytestTestEngine.php +++ b/src/unit/engine/PytestTestEngine.php @@ -1,89 +1,23 @@ resolvex(); - return $this->parseTestResults($junit_tmp); - } - - public function parseTestResults($junit_tmp) { - // xunit xsd: https://gist.github.com/959290 - $xunit_dom = new DOMDocument(); - $xunit_dom->loadXML(Filesystem::readFile($junit_tmp)); - - $results = array(); - $testcases = $xunit_dom->getElementsByTagName("testcase"); - foreach ($testcases as $testcase) { - $classname = $testcase->getAttribute("classname"); - $name = $testcase->getAttribute("name"); - $time = $testcase->getAttribute("time"); - - $status = ArcanistUnitTestResult::RESULT_PASS; - $user_data = ""; - - // A skipped test is a test which was ignored using framework - // mechanizms (e.g. @skip decorator) - $skipped = $testcase->getElementsByTagName("skipped"); - if ($skipped->length > 0) { - $status = ArcanistUnitTestResult::RESULT_SKIP; - $messages = array(); - for ($ii = 0; $ii < $skipped->length; $ii++) { - $messages[] = trim($skipped->item($ii)->nodeValue, " \n"); - } - - $user_data .= implode("\n", $messages); - } - - // Failure is a test which the code has explicitly failed by using - // the mechanizms for that purpose. e.g., via an assertEquals - $failures = $testcase->getElementsByTagName("failure"); - if ($failures->length > 0) { - $status = ArcanistUnitTestResult::RESULT_FAIL; - $messages = array(); - for ($ii = 0; $ii < $failures->length; $ii++) { - $messages[] = trim($failures->item($ii)->nodeValue, " \n"); - } - - $user_data .= implode("\n", $messages)."\n"; - } - - // An errored test is one that had an unanticipated problem. e.g., an - // unchecked throwable, or a problem with an implementation of the - // test. - $errors = $testcase->getElementsByTagName("error"); - if ($errors->length > 0) { - $status = ArcanistUnitTestResult::RESULT_BROKEN; - $messages = array(); - for ($ii = 0; $ii < $errors->length; $ii++) { - $messages[] = trim($errors->item($ii)->nodeValue, " \n"); - } - - $user_data .= implode("\n", $messages)."\n"; - } - - $result = new ArcanistUnitTestResult(); - $result->setName($classname.".".$name); - $result->setResult($status); - $result->setDuration($time); - $result->setUserData($user_data); - - $results[] = $result; - } + $parser = new ArcanistXUnitTestResultParser(); - return $results; + return $parser->parseTestResults(Filesystem::readFile($junit_tmp)); } } diff --git a/src/unit/engine/__tests__/XUnitTestResultParserTestCase.php b/src/unit/engine/__tests__/XUnitTestResultParserTestCase.php new file mode 100755 index 00000000..e752cfbe --- /dev/null +++ b/src/unit/engine/__tests__/XUnitTestResultParserTestCase.php @@ -0,0 +1,55 @@ +parseTestResults($stubbed_results); + + $this->assertEqual(0, count($parsed_results)); + } + + public function testAcceptsSimpleInput() { + $stubbed_results = Filesystem::readFile( + dirname(__FILE__).'/testresults/xunit.simple'); + $parsed_results = id(new ArcanistXUnitTestResultParser()) + ->parseTestResults($stubbed_results); + + $this->assertEqual(3, count($parsed_results)); + } + + public function testEmptyInputFailure() { + try { + $parsed_results = id(new ArcanistXUnitTestResultParser()) + ->parseTestResults(''); + + $this->failTest('Should throw on empty input'); + } catch (Exception $e) { + // OK + } + } + + public function testInvalidXmlInputFailure() { + $stubbed_results = Filesystem::readFile( + dirname(__FILE__).'/testresults/xunit.invallid-xml'); + try { + $parsed_results = id(new ArcanistXUnitTestResultParser()) + ->parseTestResults($stubbed_results); + + $this->failTest('Should throw on non-xml input'); + } catch (Exception $e) { + // OK + } + } + +} diff --git a/src/unit/engine/__tests__/testresults/xunit.invallid-xml b/src/unit/engine/__tests__/testresults/xunit.invallid-xml new file mode 100755 index 00000000..3dfdc59b --- /dev/null +++ b/src/unit/engine/__tests__/testresults/xunit.invallid-xml @@ -0,0 +1 @@ +something else diff --git a/src/unit/engine/__tests__/testresults/xunit.no-tests b/src/unit/engine/__tests__/testresults/xunit.no-tests new file mode 100755 index 00000000..11aa5365 --- /dev/null +++ b/src/unit/engine/__tests__/testresults/xunit.no-tests @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/unit/engine/__tests__/testresults/xunit.simple b/src/unit/engine/__tests__/testresults/xunit.simple new file mode 100755 index 00000000..ca3559fd --- /dev/null +++ b/src/unit/engine/__tests__/testresults/xunit.simple @@ -0,0 +1,13 @@ + + + + def test_answer(): +> assert func(3) == 5 +E assert 4 == 5 +E + where 4 = func(3) + +tests.py:5: AssertionError + + + +