Skip to content

Commit

Permalink
use xcresulttool as a special case reader in readDirectory
Browse files Browse the repository at this point in the history
  • Loading branch information
delatrie committed Feb 24, 2025
1 parent dec6f07 commit 3e2e5c7
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 19 deletions.
9 changes: 7 additions & 2 deletions packages/core/src/report.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Plugin, PluginContext, PluginState, ReportFiles, ResultFile } from "@allurereport/plugin-api";
import { allure1, allure2, attachments, cucumberjson, junitXml, xcresult } from "@allurereport/reader";
import { allure1, allure2, attachments, cucumberjson, junitXml, readXcResultBundle } from "@allurereport/reader";
import { PathResultFile, type ResultsReader } from "@allurereport/reader-api";
import console from "node:console";
import { randomUUID } from "node:crypto";
Expand Down Expand Up @@ -37,7 +37,7 @@ export class AllureReport {
constructor(opts: FullConfig) {
const {
name,
readers = [allure1, allure2, cucumberjson, junitXml, xcresult, attachments],
readers = [allure1, allure2, cucumberjson, junitXml, attachments],
plugins = [],
history,
known,
Expand Down Expand Up @@ -88,6 +88,11 @@ export class AllureReport {
}

const resultsDirPath = resolve(resultsDir);

if (await readXcResultBundle(this.#store, resultsDirPath)) {
return;
}

const dir = await opendir(resultsDirPath);

try {
Expand Down
83 changes: 83 additions & 0 deletions packages/reader/src/xcresult/bundle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { lstat } from "node:fs/promises";
import { platform } from "node:os";
import path from "node:path";
import { invokeStdoutCliTool } from "../toolRunner.js";
import { isDefined } from "../validation.js";

const XCODE_INSTALL_URL =
"https://developer.apple.com/documentation/safari-developer-tools/installing-xcode-and-simulators";

const XCODE_SWITCH_COMMAND = "sudo xcode-select -s /Applications/Xcode.app/Contents/Developer";

export const XCRESULTTOOL_MISSING_MESSAGE = `xcresulttool is required to parse Xcode Result bundles, but we can't access it on this machine.
This tool is a part of Xcode. Please make sure Xcode is installed. Visit this page to learn more about the installation:\n
${XCODE_INSTALL_URL}
Note that xcresulttool doesn't come with Command Line Tools for Xcode. You need the full Xcode package to get it. If you have both
installed, make sure the full installation is selected. If it's not, switch it with xcode-select. For example:
${XCODE_SWITCH_COMMAND}
The original error:`;

const bundleInfoFilePaths = new Set([
"Info.plist",
"Contents/Info.plist",
"Support Files/Info.plist",
"Resources/Info.plist",
]);

export const IS_MAC = platform() === "darwin";

/**
* On Mac OS returns `true` if and only if the path points to a directory that has the `"com.apple.xcode.resultbundle"`
* uniform type identifier in its content type tree.
* On other platforms return `false`.
*/
export const isXcResultBundle = async (directory: string) => {
const hasXcResultUti = IS_MAC ? await checkUniformTypeIdentifier(directory, "com.apple.xcode.resultbundle") : false;
return hasXcResultUti || isMostProbablyXcResultBundle(directory);
};

/**
* Returns `true` if and only if the path points to an item that has the specified uniform type identifier in its
* content type tree.
* Requires Mac OS.
*/
export const checkUniformTypeIdentifier = async (itemPath: string, uti: string) => {
const mdlsArgs = ["-raw", "-attr", "kMDItemContentTypeTree", itemPath];
const stringToSearch = `"${uti}"`;

for await (const line of invokeStdoutCliTool("mdls", mdlsArgs)) {
if (line.indexOf(stringToSearch) !== -1) {
return true;
}
}

return false;
};

export const isMostProbablyXcResultBundle = (directory: string) =>
isDefined(findBundleInfoFile(directory)) || followsXcResultNaming(directory);

export const followsXcResultNaming = (directory: string) => directory.endsWith(".xcresult");

/**
* If the provided directory contains an Info.plist file in one of the well-known locations, returns the absolute path
* of that file. Otherwise, returns `undefined`.
* Directories with such files are most probably Mac OS bundles and should be treated accordingly.
*
* NOTE: not all bundles contain an Info.plist file. But the ones we're interested in (XCResult bundles, specifically)
* does.
*/
export const findBundleInfoFile = async (directory: string) => {
for (const infoFilePath of bundleInfoFilePaths) {
const infoFileAbsPath = path.join(directory, infoFilePath);
const stat = await lstat(infoFileAbsPath);

if (stat.isFile()) {
return infoFileAbsPath;
}
}
};
37 changes: 20 additions & 17 deletions packages/reader/src/xcresult/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ResultFile } from "@allurereport/plugin-api";
import type { ResultsReader, ResultsVisitor } from "@allurereport/reader-api";
import type { ResultsVisitor } from "@allurereport/reader-api";
import console from "node:console";
import { IS_MAC, XCRESULTTOOL_MISSING_MESSAGE, isXcResultBundle } from "./bundle.js";
import { version } from "./xcresulttool/cli.js";
import newApi from "./xcresulttool/index.js";
import { legacyApiUnavailable } from "./xcresulttool/legacy/cli.js";
Expand All @@ -10,31 +10,34 @@ import { parseWithExportedAttachments } from "./xcresulttool/utils.js";

const readerId = "xcresult";

export const xcresult: ResultsReader = {
read: async (visitor: ResultsVisitor, data: ResultFile): Promise<boolean> => {
const resultDir = data.getOriginalFileName();
export const readXcResultBundle = async (visitor: ResultsVisitor, directory: string) => {
if (await isXcResultBundle(directory)) {
if (!IS_MAC) {
console.warn(
`It looks like ${directory} is a Mac OS bundle. Allure 3 can only parse such bundles on a Mac OS machine.`,
);

// TODO: move the check to core; replace with structural check
if (resultDir.endsWith(".xcresult")) {
if (await xcResultToolAvailable()) {
return await parseBundleWithXcResultTool(visitor, resultDir);
}
// There is a small chance we're dealing with a proper allure results directory here.
// In such a case, allow the directory to be read (if it's really a bundle, the user will see an empty report).
return false;
}

if (await xcResultToolAvailable()) {
return await parseBundleWithXcResultTool(visitor, directory);
}
return false;
},

readerId: () => readerId,
return true;
}

return false;
};

const xcResultToolAvailable = async () => {
try {
await version();
return true;
} catch (e) {
console.error(
"xcresulttool is unavailable on this machine. Please, make sure XCode is installed. The original error:",
e,
);
console.error(XCRESULTTOOL_MISSING_MESSAGE, e);
}

return false;
Expand Down

0 comments on commit 3e2e5c7

Please sign in to comment.