Skip to content

Commit

Permalink
feat: Rewrite py-ios-device client and crash reports logger into type…
Browse files Browse the repository at this point in the history
…script
  • Loading branch information
mykola-mokhnach committed Jul 3, 2024
1 parent 953055b commit d2b8794
Show file tree
Hide file tree
Showing 12 changed files with 276 additions and 256 deletions.
2 changes: 1 addition & 1 deletion lib/commands/certificate.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import path from 'path';
import http from 'http';
import {exec} from 'teen_process';
import {findAPortNotInUse, checkPortStatus} from 'portscanner';
import Pyidevice from '../py-ios-device-client';
import {Pyidevice} from '../real-device-clients/py-ios-device-client';
import {errors} from 'appium/driver';

const CONFIG_EXTENSION = 'mobileconfig';
Expand Down
3 changes: 2 additions & 1 deletion lib/commands/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,9 @@ export default {
}
if (_.isUndefined(this.logs.syslog)) {
this.logs.crashlog = new IOSCrashLog({
sim: this.device,
sim: /** @type {import('appium-ios-simulator').Simulator} */ (this.device),
udid: this.isRealDevice() ? this.opts.udid : undefined,
log: this.log,
});
this.logs.syslog = this.isRealDevice()
? new IOSDeviceLog({
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default {

const device = /** @type {import('../real-device').RealDevice} */ (this.device);

/** @type {import('../devicectl').AppInfo[]} */
/** @type {import('../real-device-clients/devicectl').AppInfo[]} */
const appInfos = await device.devicectl.listApps(bundleId);
if (_.isEmpty(appInfos)) {
throw new errors.InvalidArgumentError(
Expand Down
7 changes: 3 additions & 4 deletions lib/commands/pcap.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import Pyidevice from '../py-ios-device-client';
import {fs, tempDir, logger, util} from 'appium/support';
import { Pyidevice } from '../real-device-clients/py-ios-device-client';
import {fs, tempDir, util} from 'appium/support';
import {encodeBase64OrUpload} from '../utils';
import {errors} from 'appium/driver';

const MAX_CAPTURE_TIME_SEC = 60 * 60 * 12;
const DEFAULT_EXT = '.pcap';
const pcapLogger = logger.getLogger('pcapd');

export class TrafficCapture {
/** @type {import('teen_process').SubProcess|null} */
Expand All @@ -21,7 +20,7 @@ export class TrafficCapture {
this.mainProcess = /** @type {import('teen_process').SubProcess} */ (
await new Pyidevice(this.udid).collectPcap(this.resultPath)
);
this.mainProcess.on('line-stderr', (line) => pcapLogger.info(line));
this.mainProcess.on('line-stderr', (line) => this.log.info(`[Pcap] ${line}`));
this.log.info(
`Starting network traffic capture session on the device '${this.udid}'. ` +
`Will timeout in ${timeoutSeconds}s`,
Expand Down
146 changes: 0 additions & 146 deletions lib/device-log/ios-crash-log.js

This file was deleted.

156 changes: 156 additions & 0 deletions lib/device-log/ios-crash-log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import {fs, tempDir} from 'appium/support';
import B from 'bluebird';
import path from 'path';
import _ from 'lodash';
import {Pyidevice} from '../real-device-clients/py-ios-device-client';
import IOSLog from './ios-log';
import { toLogEntry } from './helpers';
import type { AppiumLogger } from '@appium/types';
import type { BaseDeviceClient } from '../real-device-clients/base-device-client';
import type { Simulator } from 'appium-ios-simulator';
import type { LogEntry } from '../commands/types';

const REAL_DEVICE_MAGIC = '3620bbb0-fb9f-4b62-a668-896f2edc4d88';
const MAGIC_SEP = '/';
// The file format has been changed from '.crash' to '.ips' since Monterey.
const CRASH_REPORTS_GLOB_PATTERN = '**/*.@(crash|ips)';

type TSerializedEntry = [string, number];

export interface IOSCrashLogOptions {
/** UDID of a real device */
udid?: string;
/** Simulator instance */
sim?: Simulator;
log: AppiumLogger;
}

class IOSCrashLog extends IOSLog<TSerializedEntry, TSerializedEntry> {
private readonly _udid: string | undefined;
private readonly _realDeviceClient: BaseDeviceClient | null;
private readonly _logDir: string | null;
private readonly _sim: Simulator | undefined;
private _recentCrashFiles: string[];
private _started: boolean;

constructor(opts: IOSCrashLogOptions) {
super({log: opts.log});
this._udid = opts.udid;
this._realDeviceClient = this._udid
? new Pyidevice({
udid: this._udid,
log: opts.log,
})
: null;
this._logDir = opts.udid
? null
: path.resolve(process.env.HOME || '/', 'Library', 'Logs', 'DiagnosticReports');
this._sim = opts.sim;
this._recentCrashFiles = [];
this._started = false;
}

override async startCapture(): Promise<void> {
this._recentCrashFiles = await this.listCrashFiles();
this._started = true;
}

// eslint-disable-next-line require-await
override async stopCapture(): Promise<void> {
this._started = false;
}

override get isCapturing(): boolean {
return this._started;
}

override async getLogs(): Promise<LogEntry[]> {
const crashFiles = await this.listCrashFiles();
const diffFiles = _.difference(crashFiles, this._recentCrashFiles);
if (_.isEmpty(diffFiles)) {
return [];
}

await this._serializeCrashes(diffFiles);
this._recentCrashFiles = crashFiles;
return super.getLogs();
}

protected override _serializeEntry(value: TSerializedEntry): TSerializedEntry {
return value;
}

protected override _deserializeEntry(value: TSerializedEntry): LogEntry {
const [message, timestamp] = value;
return toLogEntry(message, timestamp);
}

private async _serializeCrashes(paths: string[]): Promise<void> {
const tmpRoot = await tempDir.openDir();
try {
const promises: Promise<TSerializedEntry>[] = [];
for (const filePath of paths) {
promises.push((async () => {
let fullPath = filePath;
if (_.includes(fullPath, REAL_DEVICE_MAGIC)) {
const fileName = _.last(fullPath.split(MAGIC_SEP)) as string;
try {
await (this._realDeviceClient as BaseDeviceClient).exportCrash(fileName, tmpRoot);
} catch (e) {
this.log.warn(
`Cannot export the crash report '${fileName}'. Skipping it. ` +
`Original error: ${e.message}`,
);
return;
}
fullPath = path.join(tmpRoot, fileName);
}
const {ctime} = await fs.stat(fullPath);
return [await fs.readFile(fullPath, 'utf8'), ctime.getTime()];
})() as Promise<TSerializedEntry>);
}
for (const entry of await B.all(promises)) {
this.broadcast(entry);
}
} finally {
await fs.rimraf(tmpRoot);
}
}

private async _gatherFromRealDevice(): Promise<string[]> {
if (!this._realDeviceClient || !this._realDeviceClient.assertExists(false)) {
return [];
}

return (await this._realDeviceClient.listCrashes())
.map((x) => `${REAL_DEVICE_MAGIC}${MAGIC_SEP}${x}`);
}

private async _gatherFromSimulator(): Promise<string[]> {
if (!this._logDir || !this._sim || !(await fs.exists(this._logDir))) {
this.log.debug(`Crash reports root '${this._logDir}' does not exist. Got nothing to gather.`);
return [];
}

const foundFiles = await fs.glob(CRASH_REPORTS_GLOB_PATTERN, {
cwd: this._logDir,
absolute: true,
});
// For Simulator only include files, that contain current UDID
return await B.filter(foundFiles, async (x) => {
try {
const content = await fs.readFile(x, 'utf8');
return content.toUpperCase().includes((this._sim as Simulator).udid.toUpperCase());
} catch (err) {
return false;
}
});
}

async listCrashFiles(): Promise<string[]> {
return this._udid ? await this._gatherFromRealDevice() : await this._gatherFromSimulator();
}
}

export {IOSCrashLog};
export default IOSCrashLog;
3 changes: 2 additions & 1 deletion lib/device-log/ios-log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export abstract class IOSLog<
return this._log;
}

getLogs(): LogEntry[] {
// eslint-disable-next-line require-await
async getLogs(): Promise<LogEntry[]> {
const result: LogEntry[] = [];
for (const value of this.logs.rvalues()) {
result.push(this._deserializeEntry(value as TSerializedEntry));
Expand Down
2 changes: 1 addition & 1 deletion lib/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {desiredCapConstraints} from './desired-caps';
import DEVICE_CONNECTIONS_FACTORY from './device-connections-factory';
import {executeMethodMap} from './execute-method-map';
import {newMethodMap} from './method-map';
import Pyidevice from './py-ios-device-client';
import { Pyidevice } from './real-device-clients/py-ios-device-client';
import {
installToRealDevice,
runRealDeviceReset,
Expand Down
Loading

0 comments on commit d2b8794

Please sign in to comment.