Skip to content

Commit 202898f

Browse files
committed
优化 dumpHap 逻辑
1 parent 2dcdabd commit 202898f

File tree

4 files changed

+139
-91
lines changed

4 files changed

+139
-91
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "haptest",
3-
"version": "0.2.1",
3+
"version": "0.2.2",
44
"description": "HapTest is an OpenHarmony application UI automated testing framework.",
55
"bin": {
66
"haptest": "bin/haptest"
@@ -10,7 +10,8 @@
1010
"build": "tsc",
1111
"prepack": "npm run build",
1212
"test": "vitest",
13-
"haptest": "node -r ts-node/register src/cli/cli.ts --policy greedy_dfs -i com.huawei.hmos.files -o out"
13+
"haptest": "node -r ts-node/register src/cli/cli.ts --policy greedy_dfs -i com.huawei.hmos.files -o out",
14+
"dump": "node -r ts-node/register src/cli/cli.ts --policy perf_start_hap -i com.usb.right --exclude com.huawei.* com.ohos.* -o out"
1415
},
1516
"author": "",
1617
"license": "Apache License, Version 2.0",

src/device/device.ts

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -542,21 +542,56 @@ export class Device implements EventSimulator {
542542

543543
dumpHap(hap: Hap): void {
544544
const localPath = path.join(this.options.output, hap.bundleName);
545-
this.hdc.recvFile(`/data/app/el1/bundle/public/${hap.bundleName}`, this.options.output);
545+
const remoteBundleDir = `/data/app/el1/bundle/public/${hap.bundleName}`;
546+
if (!fs.existsSync(localPath)) {
547+
fs.mkdirSync(localPath, { recursive: true });
548+
}
549+
// 查询 bundle 目录下的文件,只拉取 .hap / .hsp 文件
550+
let hasBundleFiles = false;
551+
const bundleListOutput = this.hdc.excuteShellCommandSync(`ls ${remoteBundleDir} || true`);
552+
for (const line of bundleListOutput.split(/\r?\n/)) {
553+
const file = line.trim();
554+
if (!file) {
555+
continue;
556+
}
557+
if (file.endsWith('.hap') || file.endsWith('.hsp')) {
558+
this.hdc.recvFile(`${remoteBundleDir}/${file}`, `${localPath}/${file}`);
559+
hasBundleFiles = true;
560+
}
561+
}
546562
const pid = this.hdc.pidof(hap.bundleName);
547-
let files = new Set(this.hdc.getProcMaps(pid, /[\S]*\.h[as]{1}p$/).map((value) => value.file));
548-
for (const file of files) {
549-
this.hdc.recvFile(file, localPath);
563+
if (pid === 0) {
564+
logger.error(`dumpHap pidof ${hap.bundleName} failed`);
565+
return;
566+
}
567+
const fileReg = /[\S]*\.(hap|hsp)$/;
568+
const { maps, rawOutput } = this.hdc.getProcMapsWithRaw(pid, fileReg);
569+
570+
// 保存原始 maps 输出到本地文件
571+
const mapsFilePath = path.join(localPath, 'proc_maps.txt');
572+
fs.writeFileSync(mapsFilePath, rawOutput, 'utf-8');
573+
574+
// 如果 bundle 目录不存在或其中没有 hap/hsp 文件,则回退到根据 maps 拉取文件
575+
if (!hasBundleFiles) {
576+
let files = new Set(maps.map((value) => value.file));
577+
for (const file of files) {
578+
this.hdc.recvFile(file, localPath);
579+
}
550580
}
551581

552582
let remote = `/data/local/tmp/${hap.bundleName}_decrypt`;
553583
this.hdc.mkDir(remote);
554584

555-
this.hdc.memdump(pid, remote, /[\S]*\.h[as]{1}p$/);
585+
// 使用已获取的 maps,避免重复调用 getProcMaps
586+
this.hdc.memdump(pid, remote, fileReg, maps);
556587
this.hdc.recvFile(remote, `${localPath}/decrypt`);
557588
this.hdc.rmDir(remote);
558589
if (fs.existsSync(localPath)) {
559-
fs.renameSync(localPath, path.join(this.options.output, `${hap.bundleName}@${hap.versionName}`));
590+
const targetPath = path.join(this.options.output, `${hap.bundleName}@${hap.versionName}`);
591+
if (fs.existsSync(targetPath)) {
592+
fs.rmSync(targetPath, { recursive: true, force: true });
593+
}
594+
fs.renameSync(localPath, targetPath);
560595
}
561596
}
562597
}

src/device/hdc.ts

Lines changed: 94 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,40 @@
1313
* limitations under the License.
1414
*/
1515

16-
import { spawn, spawnSync, SpawnSyncReturns } from 'child_process';
16+
import { spawn, spawnSync, SpawnSyncReturns } from 'child_process';
1717
import path from 'path';
1818
import { convertStr2RunningState, Hap, HapRunningState } from '../model/hap';
1919
import { HdcCmdError } from '../error/error';
2020
import { getLogger } from 'log4js';
2121

2222
const logger = getLogger();
2323

24-
export const NEWLINE = /\r\n|\n/;
25-
const MEMDUMPER = '/data/local/tmp/memdumper';
26-
27-
export interface HdcTargetInfo {
28-
serial: string;
29-
transport: string;
30-
state: string;
31-
host: string;
32-
type: string;
33-
}
34-
35-
export class Hdc {
36-
private connectkey: string | undefined;
37-
38-
constructor(connectkey: string | undefined = undefined) {
39-
this.connectkey = connectkey;
40-
this.initDeviceEnv();
41-
}
42-
43-
private initDeviceEnv(): void {
44-
if (!this.hasFile(MEMDUMPER)) {
45-
let memdumpFile = path.join(__dirname, '..', '..', 'res/memdumper/memdumper');
46-
this.sendFile(memdumpFile, MEMDUMPER);
47-
this.excuteShellCommandSync(`chmod +x ${MEMDUMPER}`);
48-
}
49-
}
24+
export const NEWLINE = /\r\n|\n/;
25+
const MEMDUMPER = '/data/local/tmp/memdumper';
26+
27+
export interface HdcTargetInfo {
28+
serial: string;
29+
transport: string;
30+
state: string;
31+
host: string;
32+
type: string;
33+
}
34+
35+
export class Hdc {
36+
private connectkey: string | undefined;
37+
38+
constructor(connectkey: string | undefined = undefined) {
39+
this.connectkey = connectkey;
40+
this.initDeviceEnv();
41+
}
42+
43+
private initDeviceEnv(): void {
44+
if (!this.hasFile(MEMDUMPER)) {
45+
let memdumpFile = path.join(__dirname, '..', '..', 'res/memdumper/memdumper');
46+
this.sendFile(memdumpFile, MEMDUMPER);
47+
this.excuteShellCommandSync(`chmod +x ${MEMDUMPER}`);
48+
}
49+
}
5050

5151
sendFile(local: string, remote: string): number {
5252
let output = this.excuteSync('file', 'send', local, remote);
@@ -222,11 +222,14 @@ export class Hdc {
222222
*
223223
* @param pid process pid
224224
* @param prefix output prefix file name
225-
* @param fileReg normal app match regex: /^\/data\/storage\/el1\/bundle\/[\S]*[.hap|.hsp]$/
225+
* @param fileReg normal app match regex: /^\/data\/storage\/el1\/bundle\/.*\.(hap|hsp|so)$/
226+
* @param maps optional pre-fetched maps, if provided, will skip getProcMaps call
226227
*/
227-
memdump(pid: number, remoteOutput: string, fileReg: RegExp): void {
228+
memdump(pid: number, remoteOutput: string, fileReg: RegExp, maps?: { start: string; end: string; file: string }[]): void {
228229
let idxMap: Map<string, number> = new Map();
229-
let maps = this.getProcMaps(pid, fileReg);
230+
if (!maps) {
231+
maps = this.getProcMaps(pid, fileReg);
232+
}
230233
for (const map of maps) {
231234
let idx = 0;
232235
if (idxMap.has(map.file)) {
@@ -255,6 +258,11 @@ export class Hdc {
255258
}
256259

257260
getProcMaps(pid: number, fileReg?: RegExp): { start: string; end: string; file: string }[] {
261+
const result = this.getProcMapsWithRaw(pid, fileReg);
262+
return result.maps;
263+
}
264+
265+
getProcMapsWithRaw(pid: number, fileReg?: RegExp): { maps: { start: string; end: string; file: string }[]; rawOutput: string } {
258266
let maps: { start: string; end: string; file: string }[] = [];
259267
let cmd = `cat /proc/${pid}/maps`;
260268

@@ -272,13 +280,17 @@ export class Hdc {
272280
maps.length === 0 ||
273281
!(`0x${addr[0]}` === maps[maps.length - 1].end && file === maps[maps.length - 1].file)
274282
) {
283+
// 当前区间与上一条不连续,如该文件已存在于结果中,则忽略后续非连续映射,避免同一文件多段地址
284+
if (maps.find((m) => m.file === file)) {
285+
continue;
286+
}
275287
maps.push({ start: `0x${addr[0]}`, end: `0x${addr[1]}`, file: file });
276288
} else {
277289
maps[maps.length - 1].end = `0x${addr[1]}`;
278290
}
279291
}
280292
}
281-
return maps;
293+
return { maps, rawOutput: output };
282294
}
283295

284296
startBftp(hap: Hap): { pid: number; port: number } {
@@ -395,11 +407,11 @@ export class Hdc {
395407
return this.excute('shell', ...args);
396408
}
397409

398-
async excute(command: string, ...params: string[]): Promise<string> {
399-
return new Promise((resolve) => {
400-
let args: string[] = [];
401-
if (this.connectkey) {
402-
args.push(...['-t', this.connectkey]);
410+
async excute(command: string, ...params: string[]): Promise<string> {
411+
return new Promise((resolve) => {
412+
let args: string[] = [];
413+
if (this.connectkey) {
414+
args.push(...['-t', this.connectkey]);
403415
}
404416
args.push(...[command, ...params]);
405417
logger.debug(`hdc excute: ${JSON.stringify(args)}`);
@@ -422,49 +434,49 @@ export class Hdc {
422434
if (code !== 0) {
423435
logger.debug(`hdc process exited with code ${code}`);
424436
}
425-
});
426-
});
427-
}
428-
429-
static listTargets(): HdcTargetInfo[] {
430-
const result = spawnSync('hdc', ['list', 'targets', '-v'], { encoding: 'utf-8', shell: true });
431-
if (result.error) {
432-
throw new Error(`Failed to execute hdc: ${result.error.message}`);
433-
}
434-
435-
const stderr = (result.stderr || '').trim();
436-
const stdout = (result.stdout || '').trim();
437-
if (!stdout && stderr) {
438-
throw new Error(stderr);
439-
}
440-
441-
const entries: HdcTargetInfo[] = [];
442-
const lines = stdout.split(NEWLINE);
443-
for (const rawLine of lines) {
444-
const line = rawLine.trim();
445-
if (!line) {
446-
continue;
447-
}
448-
const lower = line.toLowerCase();
449-
if (lower.startsWith('list targets') || lower.startsWith('total')) {
450-
continue;
451-
}
452-
if (line.startsWith('[Fail]')) {
453-
throw new Error(line);
454-
}
455-
const tokens = line.split(/\s+/).filter((token) => token.length > 0);
456-
if (tokens.length < 3) {
457-
continue;
458-
}
459-
const [serial, transport = '', state = '', host = '', type = ''] = tokens;
460-
entries.push({
461-
serial,
462-
transport,
463-
state,
464-
host,
465-
type,
466-
});
467-
}
468-
return entries;
469-
}
470-
}
437+
});
438+
});
439+
}
440+
441+
static listTargets(): HdcTargetInfo[] {
442+
const result = spawnSync('hdc', ['list', 'targets', '-v'], { encoding: 'utf-8', shell: true });
443+
if (result.error) {
444+
throw new Error(`Failed to execute hdc: ${result.error.message}`);
445+
}
446+
447+
const stderr = (result.stderr || '').trim();
448+
const stdout = (result.stdout || '').trim();
449+
if (!stdout && stderr) {
450+
throw new Error(stderr);
451+
}
452+
453+
const entries: HdcTargetInfo[] = [];
454+
const lines = stdout.split(NEWLINE);
455+
for (const rawLine of lines) {
456+
const line = rawLine.trim();
457+
if (!line) {
458+
continue;
459+
}
460+
const lower = line.toLowerCase();
461+
if (lower.startsWith('list targets') || lower.startsWith('total')) {
462+
continue;
463+
}
464+
if (line.startsWith('[Fail]')) {
465+
throw new Error(line);
466+
}
467+
const tokens = line.split(/\s+/).filter((token) => token.length > 0);
468+
if (tokens.length < 3) {
469+
continue;
470+
}
471+
const [serial, transport = '', state = '', host = '', type = ''] = tokens;
472+
entries.push({
473+
serial,
474+
transport,
475+
state,
476+
host,
477+
type,
478+
});
479+
}
480+
return entries;
481+
}
482+
}

test/unit/hdc.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* limitations under the License.
1414
*/
1515

16-
import { vi, describe, it, expect } from 'vitest';
16+
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
1717
import { Hdc } from '../../src/device/hdc';
1818
import * as path from 'path';
1919
import fs from 'fs';

0 commit comments

Comments
 (0)