Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


SSL multipass.runtime.ts

  Interaktion und
PortierbarkeitJAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import { execFile } from "node:child_process";
import { randomUUID } from "node:crypto";
import fs from "node:fs";
import { access, appendFile, mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
import type { QaProviderMode } from "./model-selection.js";
import { resolveQaForwardedLiveEnv, resolveQaLiveProviderConfigPath } from "./providers/env.js";
import { DEFAULT_QA_LIVE_PROVIDER_MODE, getQaProvider } from "./providers/index.js";

const MULTIPASS_MOUNTED_REPO_PATH = "/workspace/openclaw-host";
const MULTIPASS_GUEST_REPO_PATH = "/workspace/openclaw";
const MULTIPASS_GUEST_CODEX_HOME_PATH = "/workspace/openclaw-codex-home";
const MULTIPASS_GUEST_PACKAGES = [
  "build-essential",
  "ca-certificates",
  "curl",
  "pkg-config",
  "python3",
  "rsync",
  "xz-utils",
] as const;
const MULTIPASS_REPO_SYNC_EXCLUDES = [
  ".git",
  "node_modules",
  ".artifacts",
  ".tmp",
  ".turbo",
  "coverage",
  "*.heapsnapshot",
] as const;
const MULTIPASS_EXEC_MAX_BUFFER = 64 * 1024 * 1024;
const MULTIPASS_GUEST_RUN_TIMEOUT_MS = 60 * 60 * 1000;

export const qaMultipassDefaultResources = {
  image: "lts",
  cpus: 2,
  memory: "4G",
  disk: "24G",
} as const;

type ExecResult = {
  stdout: string;
  stderr: string;
};

type ExecFileError = Error & {
  code?: string;
};

type ExecFileOptions = {
  timeoutMs?: number;
};

export type QaMultipassPlan = {
  repoRoot: string;
  outputDir: string;
  reportPath: string;
  summaryPath: string;
  hostLogPath: string;
  hostBootstrapLogPath: string;
  hostGuestScriptPath: string;
  vmName: string;
  image: string;
  cpus: number;
  memory: string;
  disk: string;
  pnpmVersion: string;
  transportId: string;
  providerMode: QaProviderMode;
  primaryModel?: string;
  alternateModel?: string;
  fastMode?: boolean;
  thinkingDefault?: string;
  scenarioIds: string[];
  forwardedEnv: Record<string, string>;
  hostCodexHomePath?: string;
  guestCodexHomePath?: string;
  hostLiveProviderConfigPath?: string;
  guestLiveProviderConfigPath?: string;
  guestMountedRepoPath: string;
  guestRepoPath: string;
  guestOutputDir: string;
  guestScriptPath: string;
  guestBootstrapLogPath: string;
  qaCommand: string[];
};

export type QaMultipassRunResult = {
  outputDir: string;
  reportPath: string;
  summaryPath: string;
  hostLogPath: string;
  bootstrapLogPath: string;
  guestScriptPath: string;
  vmName: string;
  scenarioIds: string[];
};

type RenderGuestScriptOptions = {
  redactSecrets?: boolean;
};

function shellQuote(value: string) {
  return `'${value.replaceAll("'", `'"'"'`)}'`;
}

function createOutputStamp() {
  return new Date().toISOString().replaceAll(":", "").replaceAll(".", "").replace("T", "-");
}

function createVmSuffix() {
  return `${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
}

function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function execFileAsync(file: string, args: string[], options: ExecFileOptions = {}) {
  return new Promise<ExecResult>((resolve, reject) => {
    execFile(
      file,
      args,
      {
        encoding: "utf8",
        maxBuffer: MULTIPASS_EXEC_MAX_BUFFER,
        timeout: options.timeoutMs,
      },
      (error, stdout, stderr) => {
        if (error) {
          const message = stderr.trim() || stdout.trim() || error.message;
          const wrappedError = new Error(message, { cause: error }) as ExecFileError;
          wrappedError.code = (error as NodeJS.ErrnoException).code;
          reject(wrappedError);
          return;
        }
        resolve({ stdout, stderr });
      },
    );
  });
}

function resolveRealPath(value: string) {
  return fs.realpathSync.native?.(value) ?? fs.realpathSync(value);
}

function resolveExistingPath(value: string) {
  let currentPath = value;
  while (!fs.existsSync(currentPath)) {
    const parentPath = path.dirname(currentPath);
    if (parentPath === currentPath) {
      throw new Error(`unable to resolve existing path for ${value}`);
    }
    currentPath = parentPath;
  }
  return currentPath;
}

function isPathInside(parentPath: string, childPath: string) {
  const relativePath = path.relative(parentPath, childPath);
  return !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
}

function validatePnpmVersion(version: string) {
  if (!/^[0-9A-Za-z.+_-]+$/u.test(version)) {
    throw new Error(`unsupported pnpm version in packageManager: ${version}`);
  }
  return version;
}

function resolveMountedOutputPath(repoRoot: string, hostPath: string) {
  const relativePath = path.relative(repoRoot, hostPath);
  if (relativePath.startsWith("..") || path.isAbsolute(relativePath) || relativePath.length === 0) {
    throw new Error(
      `qa suite --runner multipass requires --output-dir to stay under the repo root (${repoRoot}), got ${hostPath}.`,
    );
  }

  const realRepoRoot = resolveRealPath(repoRoot);
  const existingHostPath = resolveExistingPath(hostPath);
  const realExistingHostPath = resolveRealPath(existingHostPath);
  if (!isPathInside(realRepoRoot, realExistingHostPath) && realExistingHostPath !== realRepoRoot) {
    throw new Error(
      `qa suite --runner multipass requires --output-dir to stay under the repo root (${repoRoot}), got ${hostPath}.`,
    );
  }

  return path.posix.join(MULTIPASS_MOUNTED_REPO_PATH, ...relativePath.split(path.sep));
}

function resolvePnpmVersion(repoRoot: string) {
  const packageJsonPath = path.join(repoRoot, "package.json");
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
    packageManager?: string;
  };
  const packageManager = packageJson.packageManager ?? "";
  const match = /^pnpm@(.+)$/.exec(packageManager);
  if (!match?.[1]) {
    throw new Error(`unable to resolve pnpm version from packageManager in ${packageJsonPath}`);
  }
  return match[1];
}

function resolveMultipassInstallHint() {
  if (process.platform === "darwin") {
    return "brew install --cask multipass";
  }
  if (process.platform === "win32") {
    return "winget install Canonical.Multipass";
  }
  if (process.platform === "linux") {
    return "sudo snap install multipass";
  }
  return "https://multipass.run/install";
}

function createQaMultipassOutputDir(repoRoot: string) {
  return path.join(repoRoot, ".artifacts", "qa-e2e", `multipass-${createOutputStamp()}`);
}

function resolveGuestMountedPath(repoRoot: string, hostPath: string) {
  return resolveMountedOutputPath(repoRoot, hostPath);
}

function appendScenarioArgs(command: string[], scenarioIds: string[]) {
  for (const scenarioId of scenarioIds) {
    command.push("--scenario", scenarioId);
  }
  return command;
}

export function createQaMultipassPlan(params: {
  repoRoot: string;
  outputDir?: string;
  transportId?: string;
  providerMode?: QaProviderMode;
  primaryModel?: string;
  alternateModel?: string;
  fastMode?: boolean;
  thinkingDefault?: string;
  allowFailures?: boolean;
  scenarioIds?: string[];
  concurrency?: number;
  image?: string;
  cpus?: number;
  memory?: string;
  disk?: string;
}) {
  const outputDir = params.outputDir ?? createQaMultipassOutputDir(params.repoRoot);
  const scenarioIds = [...new Set(params.scenarioIds ?? [])];
  const transportId = params.transportId?.trim() || "qa-channel";
  const providerMode = params.providerMode ?? DEFAULT_QA_LIVE_PROVIDER_MODE;
  const provider = getQaProvider(providerMode);
  const forwardedEnv = provider.appliesLiveEnvAliases ? resolveQaForwardedLiveEnv() : {};
  const hostCodexHomePath = forwardedEnv.CODEX_HOME;
  const liveProviderConfig = provider.usesModelProviderPlugins
    ? resolveQaLiveProviderConfigPath()
    : undefined;
  const hostLiveProviderConfigPath =
    liveProviderConfig && fs.existsSync(liveProviderConfig.path)
      ? liveProviderConfig.path
      : undefined;
  const vmName = `openclaw-qa-${createVmSuffix()}`;
  const guestOutputDir = resolveGuestMountedPath(params.repoRoot, outputDir);
  const qaCommand = appendScenarioArgs(
    [
      "pnpm",
      "openclaw",
      "qa",
      "suite",
      "--transport",
      transportId,
      "--provider-mode",
      providerMode,
      "--output-dir",
      guestOutputDir,
      ...(params.primaryModel ? ["--model", params.primaryModel] : []),
      ...(params.alternateModel ? ["--alt-model", params.alternateModel] : []),
      ...(params.fastMode ? ["--fast"] : []),
      ...(params.thinkingDefault ? ["--thinking", params.thinkingDefault] : []),
      ...(params.allowFailures ? ["--allow-failures"] : []),
      ...(params.concurrency ? ["--concurrency", String(params.concurrency)] : []),
    ],
    scenarioIds,
  );

  return {
    repoRoot: params.repoRoot,
    outputDir,
    reportPath: path.join(outputDir, "qa-suite-report.md"),
    summaryPath: path.join(outputDir, "qa-suite-summary.json"),
    hostLogPath: path.join(outputDir, "multipass-host.log"),
    hostBootstrapLogPath: path.join(outputDir, "multipass-guest-bootstrap.log"),
    hostGuestScriptPath: path.join(outputDir, "multipass-guest-run.sh"),
    vmName,
    image: params.image ?? qaMultipassDefaultResources.image,
    cpus: params.cpus ?? qaMultipassDefaultResources.cpus,
    memory: params.memory ?? qaMultipassDefaultResources.memory,
    disk: params.disk ?? qaMultipassDefaultResources.disk,
    pnpmVersion: validatePnpmVersion(resolvePnpmVersion(params.repoRoot)),
    transportId,
    providerMode,
    primaryModel: params.primaryModel,
    alternateModel: params.alternateModel,
    fastMode: params.fastMode,
    thinkingDefault: params.thinkingDefault,
    scenarioIds,
    forwardedEnv,
    hostCodexHomePath,
    guestCodexHomePath: hostCodexHomePath ? MULTIPASS_GUEST_CODEX_HOME_PATH : undefined,
    hostLiveProviderConfigPath,
    guestLiveProviderConfigPath: hostLiveProviderConfigPath
      ? `/tmp/${vmName}-live-provider-config.json`
      : undefined,
    guestMountedRepoPath: MULTIPASS_MOUNTED_REPO_PATH,
    guestRepoPath: MULTIPASS_GUEST_REPO_PATH,
    guestOutputDir,
    guestScriptPath: `/tmp/${vmName}-qa-suite.sh`,
    guestBootstrapLogPath: `/tmp/${vmName}-bootstrap.log`,
    qaCommand,
  } satisfies QaMultipassPlan;
}

export function renderQaMultipassGuestScript(
  plan: QaMultipassPlan,
  options: RenderGuestScriptOptions = {},
) {
  const redactSecrets = options.redactSecrets ?? false;
  const rsyncCommand = [
    "rsync -a --delete",
    ...MULTIPASS_REPO_SYNC_EXCLUDES.flatMap((value) => ["--exclude", shellQuote(value)]),
    shellQuote(`${plan.guestMountedRepoPath}/`),
    shellQuote(`${plan.guestRepoPath}/`),
  ].join(" ");
  const qaCommand = [
    ...Object.entries(plan.forwardedEnv)
      .filter(
        ([key]) =>
          key !== "CODEX_HOME" &&
          key !== "OPENCLAW_CONFIG_PATH" &&
          key !== "OPENCLAW_QA_LIVE_PROVIDER_CONFIG_PATH",
      )
      .map(([key, value]) => `${key}=${shellQuote(redactSecrets ? "<redacted>" : value)}`),
    ...(plan.guestCodexHomePath ? [`CODEX_HOME=${shellQuote(plan.guestCodexHomePath)}`] : []),
    ...(plan.guestLiveProviderConfigPath
      ? [
          `OPENCLAW_CONFIG_PATH=${shellQuote(plan.guestLiveProviderConfigPath)}`,
          `OPENCLAW_QA_LIVE_PROVIDER_CONFIG_PATH=${shellQuote(plan.guestLiveProviderConfigPath)}`,
        ]
      : []),
    plan.qaCommand.map(shellQuote).join(" "),
  ].join(" ");

  const lines = [
    "#!/usr/bin/env bash",
    "set -euo pipefail",
    "trap 'status=$?; echo \"guest failure (exit ${status})\" >&2; exit ${status}' ERR",
    "",
    "export DEBIAN_FRONTEND=noninteractive",
    `BOOTSTRAP_LOG=${shellQuote(plan.guestBootstrapLogPath)}`,
    ': > "$BOOTSTRAP_LOG"',
    "",
    "ensure_guest_packages() {",
    '  sudo -E apt-get update >>"$BOOTSTRAP_LOG" 2>&1',
    "  sudo -E apt-get install -y \\",
    ...MULTIPASS_GUEST_PACKAGES.map((value, index) =>
      index === MULTIPASS_GUEST_PACKAGES.length - 1
        ? `    ${value} >>"$BOOTSTRAP_LOG" 2>&1`
        : `    ${value} \\`,
    ),
    "}",
    "",
    "ensure_node() {",
    "  if command -v node >/dev/null; then",
    "    local node_major",
    '    node_major="$(node -p \'process.versions.node.split(".")[0]\' 2>/dev/null || echo 0)"',
    '    if [ "${node_major}" -ge 22 ]; then',
    "      return 0",
    "    fi",
    "  fi",
    "  local node_arch",
    '  case "$(uname -m)" in',
    '    x86_64) node_arch="x64" ;;',
    '    aarch64|arm64) node_arch="arm64" ;;',
    '    *) echo "unsupported guest architecture for node bootstrap: $(uname -m)" >&2; return 1 ;;',
    "  esac",
    "  local node_tmp_dir tarball_name extract_dir base_url",
    '  node_tmp_dir="$(mktemp -d)"',
    "  trap 'rm -rf \"${node_tmp_dir}\"' RETURN",
    '  base_url="https://nodejs.org/dist/latest-v22.x"',
    '  curl -fsSL "${base_url}/SHASUMS256.txt" -o "${node_tmp_dir}/SHASUMS256.txt" >>"$BOOTSTRAP_LOG" 2>&1',
    '  tarball_name="$(awk \'/linux-\'"${node_arch}"\'\\.tar\\.xz$/ { print $2; exit }\' "${node_tmp_dir}/SHASUMS256.txt")"',
    '  [ -n "${tarball_name}" ] || { echo "unable to resolve node tarball for ${node_arch}" >&2; return 1; }',
    '  curl -fsSL "${base_url}/${tarball_name}" -o "${node_tmp_dir}/${tarball_name}" >>"$BOOTSTRAP_LOG" 2>&1',
    '  (cd "${node_tmp_dir}" && grep " ${tarball_name}$" SHASUMS256.txt | sha256sum -c -) >>"$BOOTSTRAP_LOG" 2>&1',
    '  extract_dir="${tarball_name%.tar.xz}"',
    '  sudo mkdir -p /usr/local/lib/nodejs >>"$BOOTSTRAP_LOG" 2>&1',
    '  sudo rm -rf "/usr/local/lib/nodejs/${extract_dir}" >>"$BOOTSTRAP_LOG" 2>&1',
    '  sudo tar -xJf "${node_tmp_dir}/${tarball_name}" -C /usr/local/lib/nodejs >>"$BOOTSTRAP_LOG" 2>&1',
    '  sudo ln -sf "/usr/local/lib/nodejs/${extract_dir}/bin/node" /usr/local/bin/node >>"$BOOTSTRAP_LOG" 2>&1',
    '  sudo ln -sf "/usr/local/lib/nodejs/${extract_dir}/bin/npm" /usr/local/bin/npm >>"$BOOTSTRAP_LOG" 2>&1',
    '  sudo ln -sf "/usr/local/lib/nodejs/${extract_dir}/bin/npx" /usr/local/bin/npx >>"$BOOTSTRAP_LOG" 2>&1',
    '  sudo ln -sf "/usr/local/lib/nodejs/${extract_dir}/bin/corepack" /usr/local/bin/corepack >>"$BOOTSTRAP_LOG" 2>&1',
    "}",
    "",
    "ensure_pnpm() {",
    '  sudo env PATH="/usr/local/bin:/usr/bin:/bin" corepack enable >>"$BOOTSTRAP_LOG" 2>&1',
    `  sudo env PATH="/usr/local/bin:/usr/bin:/bin" corepack prepare ${shellQuote(`pnpm@${plan.pnpmVersion}`)} --activate >>"$BOOTSTRAP_LOG" 2>&1`,
    "}",
    "",
    'command -v sudo >/dev/null || { echo "missing sudo in guest" >&2; exit 1; }',
    "ensure_guest_packages",
    "ensure_node",
    "ensure_pnpm",
    'command -v node >/dev/null || { echo "missing node after guest bootstrap" >&2; exit 1; }',
    'command -v pnpm >/dev/null || { echo "missing pnpm after guest bootstrap" >&2; exit 1; }',
    'command -v rsync >/dev/null || { echo "missing rsync after guest bootstrap" >&2; exit 1; }',
    "",
    `mkdir -p ${shellQuote(path.posix.dirname(plan.guestRepoPath))}`,
    `rm -rf ${shellQuote(plan.guestRepoPath)}`,
    `mkdir -p ${shellQuote(plan.guestRepoPath)}`,
    `mkdir -p ${shellQuote(plan.guestOutputDir)}`,
    rsyncCommand,
    `cd ${shellQuote(plan.guestRepoPath)}`,
    'pnpm install --frozen-lockfile >>"$BOOTSTRAP_LOG" 2>&1',
    'pnpm build >>"$BOOTSTRAP_LOG" 2>&1',
    qaCommand,
    "",
  ];
  return lines.join("\n");
}

async function appendMultipassLog(logPath: string, message: string) {
  await appendFile(logPath, message, "utf8");
}

async function runMultipassCommand(logPath: string, args: string[], options: ExecFileOptions = {}) {
  await appendMultipassLog(logPath, `$ ${["multipass", ...args].join(" ")}\n`);
  const result = await execFileAsync("multipass", args, options);
  if (result.stdout.trim()) {
    await appendMultipassLog(logPath, `${result.stdout.trim()}\n`);
  }
  if (result.stderr.trim()) {
    await appendMultipassLog(logPath, `${result.stderr.trim()}\n`);
  }
  await appendMultipassLog(logPath, "\n");
  return result;
}

async function waitForGuestReady(logPath: string, vmName: string) {
  let lastError: unknown;
  for (let attempt = 1; attempt <= 12; attempt += 1) {
    try {
      await runMultipassCommand(logPath, ["exec", vmName, "--", "bash", "-lc", "echo guest-ready"]);
      return;
    } catch (error) {
      lastError = error;
      await appendMultipassLog(
        logPath,
        `guest-ready retry ${attempt}/12: ${error instanceof Error ? error.message : String(error)}\n\n`,
      );
      if (attempt < 12) {
        await sleep(2_000);
      }
    }
  }
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
}

async function mountRepo(logPath: string, repoRoot: string, vmName: string) {
  let lastError: unknown;
  for (let attempt = 1; attempt <= 5; attempt += 1) {
    try {
      await runMultipassCommand(logPath, [
        "mount",
        repoRoot,
        `${vmName}:${MULTIPASS_MOUNTED_REPO_PATH}`,
      ]);
      return;
    } catch (error) {
      lastError = error;
      await appendMultipassLog(
        logPath,
        `mount retry ${attempt}/5: ${error instanceof Error ? error.message : String(error)}\n\n`,
      );
      if (attempt < 5) {
        await sleep(2_000);
      }
    }
  }
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
}

async function mountCodexHome(logPath: string, hostCodexHomePath: string, vmName: string) {
  let lastError: unknown;
  for (let attempt = 1; attempt <= 5; attempt += 1) {
    try {
      await runMultipassCommand(logPath, [
        "mount",
        hostCodexHomePath,
        `${vmName}:${MULTIPASS_GUEST_CODEX_HOME_PATH}`,
      ]);
      return;
    } catch (error) {
      lastError = error;
      await appendMultipassLog(
        logPath,
        `codex-home mount retry ${attempt}/5: ${error instanceof Error ? error.message : String(error)}\n\n`,
      );
      if (attempt < 5) {
        await sleep(2_000);
      }
    }
  }
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
}

async function transferLiveProviderConfig(plan: QaMultipassPlan) {
  if (!plan.hostLiveProviderConfigPath || !plan.guestLiveProviderConfigPath) {
    return;
  }
  await runMultipassCommand(plan.hostLogPath, [
    "transfer",
    plan.hostLiveProviderConfigPath,
    `${plan.vmName}:${plan.guestLiveProviderConfigPath}`,
  ]);
}

async function tryCopyGuestBootstrapLog(plan: QaMultipassPlan) {
  try {
    await runMultipassCommand(plan.hostLogPath, [
      "transfer",
      `${plan.vmName}:${plan.guestBootstrapLogPath}`,
      plan.hostBootstrapLogPath,
    ]);
  } catch (error) {
    await appendMultipassLog(
      plan.hostLogPath,
      `bootstrap log transfer skipped: ${error instanceof Error ? error.message : String(error)}\n\n`,
    );
  }
}

export async function runQaMultipass(params: {
  repoRoot: string;
  outputDir?: string;
  transportId?: string;
  providerMode?: QaProviderMode;
  primaryModel?: string;
  alternateModel?: string;
  fastMode?: boolean;
  allowFailures?: boolean;
  scenarioIds?: string[];
  concurrency?: number;
  image?: string;
  cpus?: number;
  memory?: string;
  disk?: string;
}) {
  const plan = createQaMultipassPlan(params);
  await mkdir(plan.outputDir, { recursive: true });
  await writeFile(
    plan.hostLogPath,
    `# OpenClaw QA Multipass host log\nvmName=${plan.vmName}\noutputDir=${plan.outputDir}\n\n`,
    "utf8",
  );
  await writeFile(
    plan.hostGuestScriptPath,
    renderQaMultipassGuestScript(plan, { redactSecrets: true }),
    {
      encoding: "utf8",
      mode: 0o600,
    },
  );

  try {
    await execFileAsync("multipass", ["version"]);
  } catch (error) {
    if ((error as ExecFileError).code !== "ENOENT") {
      throw new Error(
        `Unable to verify Multipass availability: ${error instanceof Error ? error.message : String(error)}.`,
        { cause: error },
      );
    }
    throw new Error(
      `Multipass is not installed on this host. Install it with '${resolveMultipassInstallHint()}', then rerun 'pnpm openclaw qa suite --runner multipass'.`,
      { cause: error },
    );
  }

  const hostTransferDirPath = await fs.promises.mkdtemp(
    path.join(resolvePreferredOpenClawTmpDir(), `${plan.vmName}-qa-suite-`),
  );
  const hostTransferScriptPath = path.join(hostTransferDirPath, "guest-run.sh");
  await writeFile(hostTransferScriptPath, renderQaMultipassGuestScript(plan), {
    encoding: "utf8",
    mode: 0o600,
  });

  let launched = false;
  try {
    await runMultipassCommand(plan.hostLogPath, [
      "launch",
      "--name",
      plan.vmName,
      "--cpus",
      String(plan.cpus),
      "--memory",
      plan.memory,
      "--disk",
      plan.disk,
      plan.image,
    ]);
    launched = true;
    await waitForGuestReady(plan.hostLogPath, plan.vmName);
    await mountRepo(plan.hostLogPath, plan.repoRoot, plan.vmName);
    if (plan.hostCodexHomePath) {
      await mountCodexHome(plan.hostLogPath, plan.hostCodexHomePath, plan.vmName);
    }
    await transferLiveProviderConfig(plan);
    await runMultipassCommand(plan.hostLogPath, [
      "transfer",
      hostTransferScriptPath,
      `${plan.vmName}:${plan.guestScriptPath}`,
    ]);
    await runMultipassCommand(plan.hostLogPath, [
      "exec",
      plan.vmName,
      "--",
      "chmod",
      "+x",
      plan.guestScriptPath,
    ]);
    await runMultipassCommand(plan.hostLogPath, ["exec", plan.vmName, "--", plan.guestScriptPath], {
      timeoutMs: MULTIPASS_GUEST_RUN_TIMEOUT_MS,
    });
    await tryCopyGuestBootstrapLog(plan);
  } catch (error) {
    if (launched) {
      await tryCopyGuestBootstrapLog(plan);
    }
    throw new Error(
      `QA Multipass run failed: ${error instanceof Error ? error.message : String(error)}. See ${plan.hostLogPath}.`,
      { cause: error },
    );
  } finally {
    await fs.promises.rm(hostTransferDirPath, { recursive: true, force: true });
    if (launched) {
      try {
        await runMultipassCommand(plan.hostLogPath, ["delete", "--purge", plan.vmName]);
      } catch (error) {
        await appendMultipassLog(
          plan.hostLogPath,
          `cleanup error: ${error instanceof Error ? error.message : String(error)}\n\n`,
        );
      }
    }
  }

  await access(plan.reportPath);
  await access(plan.summaryPath);

  return {
    outputDir: plan.outputDir,
    reportPath: plan.reportPath,
    summaryPath: plan.summaryPath,
    hostLogPath: plan.hostLogPath,
    bootstrapLogPath: plan.hostBootstrapLogPath,
    guestScriptPath: plan.hostGuestScriptPath,
    vmName: plan.vmName,
    scenarioIds: plan.scenarioIds,
  } satisfies QaMultipassRunResult;
}

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.23Angebot  (Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-04-27) ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge