Skip to content

Commit

Permalink
Merge pull request #10461 from OfficeDev/aochengwang/testtool-binary2
Browse files Browse the repository at this point in the history
feat: expose test tool checker for VS
  • Loading branch information
a1exwang authored Nov 30, 2023
2 parents c8030e7 + 509b586 commit 9a338f7
Show file tree
Hide file tree
Showing 8 changed files with 388 additions and 1 deletion.
55 changes: 55 additions & 0 deletions packages/fx-core/src/common/deps-checker/coreDepsLoggerAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { LogLevel, LogProvider } from "@microsoft/teamsfx-api";
import os from "os";
import { DepsLogger } from "./depsLogger";

export class CoreDepsLoggerAdapter implements DepsLogger {
private detailLogLines: string[] = [];
private logProvider: LogProvider;

public constructor(logProvider: LogProvider) {
this.logProvider = logProvider;
}

public debug(message: string): void {
this.addToDetailCache(LogLevel.Debug, message);
}

public info(message: string): void {
this.addToDetailCache(LogLevel.Info, message);
this.logProvider.info(message);
}

public warning(message: string): void {
this.addToDetailCache(LogLevel.Warning, message);
this.logProvider.warning(message);
}

public error(message: string): void {
this.addToDetailCache(LogLevel.Error, message);
this.logProvider.error(message);
}

public appendLine(message: string): void {
this.logProvider.log(LogLevel.Info, message);
}

public append(message: string): void {
this.logProvider.log(LogLevel.Info, message);
}

public cleanup(): void {
this.detailLogLines = [];
}

public printDetailLog(): void {
this.logProvider.error(this.detailLogLines.join(os.EOL));
}

private addToDetailCache(level: LogLevel, message: string): void {
const line = `${String(LogLevel[level])} ${new Date().toISOString()}: ${message}`;
this.detailLogLines.push(line);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { DepsTelemetry } from "./depsTelemetry";
import { SystemError, TelemetryReporter, UserError } from "@microsoft/teamsfx-api";
import os from "os";
import { DepsCheckerEvent, TelemetryMessurement } from "./constant";
import { TelemetryProperty, fillInTelemetryPropsForFxError } from "../telemetry";
import { TelemetryMeasurement } from "../../component/utils/depsChecker/common";

export class CoreDepsTelemetryAdapter implements DepsTelemetry {
private readonly _telemetryComponentType = "core:debug:envchecker";
private readonly _telemetryReporter: TelemetryReporter;

constructor(telemetryReporter: TelemetryReporter) {
this._telemetryReporter = telemetryReporter;
}

public sendEvent(
eventName: DepsCheckerEvent,
properties: { [key: string]: string } = {},
timecost?: number
): void {
this.addCommonProps(properties);
const measurements: { [p: string]: number } = {};

if (timecost) {
measurements[TelemetryMessurement.completionTime] = timecost;
}

this._telemetryReporter.sendTelemetryEvent(eventName, properties, measurements);
}

public async sendEventWithDuration(
eventName: DepsCheckerEvent,
action: () => Promise<void>
): Promise<void> {
const start = performance.now();
await action();
// use seconds instead of milliseconds
const timecost = Number(((performance.now() - start) / 1000).toFixed(2));
this.sendEvent(eventName, {}, timecost);
}

public sendUserErrorEvent(eventName: DepsCheckerEvent, errorMessage: string): void {
const properties: { [key: string]: string } = {};
const error = new UserError(this._telemetryComponentType, eventName, errorMessage);
fillInTelemetryPropsForFxError(properties, error);
this._telemetryReporter.sendTelemetryErrorEvent(eventName, {
...this.addCommonProps(),
...properties,
});
}

public sendSystemErrorEvent(
eventName: DepsCheckerEvent,
errorMessage: string,
errorStack: string
): void {
const properties: { [key: string]: string } = {};
const error = new SystemError(
this._telemetryComponentType,
eventName,
`errorMsg=${errorMessage},errorStack=${errorStack}`
);
error.stack = errorStack;
fillInTelemetryPropsForFxError(properties, error);
this._telemetryReporter.sendTelemetryErrorEvent(eventName, {
...this.addCommonProps(),
...properties,
});
}

private addCommonProps(properties: { [key: string]: string } = {}): { [key: string]: string } {
properties[TelemetryMeasurement.OSArch] = os.arch();
properties[TelemetryMeasurement.OSRelease] = os.release();
properties[TelemetryProperty.Component] = this._telemetryComponentType;
return properties;
}
}
2 changes: 2 additions & 0 deletions packages/fx-core/src/common/deps-checker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ export * from "./depsManager";
export * from "./depsTelemetry";
export * from "./constant";
export * from "./util";
export * from "./coreDepsLoggerAdapter";
export * from "./coreDepsTelemetryAdapter";
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

/**
* @author Aocheng Wang <aochengwang@microsoft.com>
*/
import "mocha";

import chai from "chai";
import * as sinon from "sinon";
import { LogProvider } from "@microsoft/teamsfx-api";
import { CoreDepsLoggerAdapter } from "../../../src/common/deps-checker/coreDepsLoggerAdapter";

describe("CoreDepsLoggerAdapter", () => {
const sandbox = sinon.createSandbox();

afterEach(() => {
sandbox.restore();
});

it("append", () => {
// Arrange
let text = "";
const stub = sandbox.stub().callsFake((_level, _text: string) => (text = _text));
const logProvider = { log: stub } as any as LogProvider;

// Act
const adapter = new CoreDepsLoggerAdapter(logProvider);
adapter.append("test");

// Assert
chai.assert.match(text, /test/);
});

it("appendLine", () => {
// Arrange
let text = "";
const stub = sandbox.stub().callsFake((_level, _text: string) => (text = _text));
const logProvider = { log: stub } as any as LogProvider;

// Act
const adapter = new CoreDepsLoggerAdapter(logProvider);
adapter.appendLine("test");

// Assert
chai.assert.match(text, /test/);
});

it("error", () => {
// Arrange
let text = "";
const stub = sandbox.stub().callsFake((_text: string) => (text = _text));
const logProvider = { error: stub } as any as LogProvider;

// Act
const adapter = new CoreDepsLoggerAdapter(logProvider);
adapter.error("test");

// Assert
chai.assert.match(text, /test/);
});

it("info", () => {
// Arrange
let text = "";
const stub = sandbox.stub().callsFake((_text: string) => (text = _text));
const logProvider = { info: stub } as any as LogProvider;

// Act
const adapter = new CoreDepsLoggerAdapter(logProvider);
adapter.info("test");

// Assert
chai.assert.match(text, /test/);
});

it("warning", () => {
// Arrange
let text = "";
const stub = sandbox.stub().callsFake((_text: string) => (text = _text));
const logProvider = { warning: stub } as any as LogProvider;

// Act
const adapter = new CoreDepsLoggerAdapter(logProvider);
adapter.warning("test");

// Assert
chai.assert.match(text, /test/);
});

it("debug", () => {
// Arrange
let text = "";
const stub = sandbox.stub().callsFake((_text: string) => (text = _text));
const logProvider = { error: stub } as any as LogProvider;

// Act
const adapter = new CoreDepsLoggerAdapter(logProvider);
adapter.debug("error");
adapter.printDetailLog();

// Assert
chai.assert.match(text, /error/);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

/**
* @author Aocheng Wang <aochengwang@microsoft.com>
*/
import "mocha";

import chai from "chai";
import os from "os";
import * as sinon from "sinon";
import { TelemetryReporter } from "@microsoft/teamsfx-api";
import { CoreDepsTelemetryAdapter } from "../../../src/common/deps-checker/coreDepsTelemetryAdapter";
import { DepsCheckerEvent } from "../../../src/common/deps-checker/constant";

describe("CoreDepsTelemetryAdapter", () => {
const sandbox = sinon.createSandbox();

beforeEach(() => {
sandbox.stub(os, "arch").returns("mock");
sandbox.stub(os, "release").returns("mock");
});

afterEach(() => {
sandbox.restore();
});

it("sendEvent", () => {
// Arrange
const stub = sandbox.stub();
const reporter = { sendTelemetryEvent: stub } as any as TelemetryReporter;

// Act
const adapter = new CoreDepsTelemetryAdapter(reporter);
adapter.sendEvent(DepsCheckerEvent.dotnetAlreadyInstalled, { property1: "value1" }, 42);

// Assert
sinon.assert.calledWith(
stub,
DepsCheckerEvent.dotnetAlreadyInstalled,
{
component: "core:debug:envchecker",
["os-arch"]: "mock",
["os-release"]: "mock",
property1: "value1",
},
{ ["completion-time"]: 42 }
);
});
it("sendUserErrorEvent", () => {
// Arrange
let eventName = "";
const telemetryReporter = {
sendTelemetryErrorEvent(_eventName: string) {
eventName = _eventName;
},
} as any as TelemetryReporter;

// Act
const adapter = new CoreDepsTelemetryAdapter(telemetryReporter);
adapter.sendUserErrorEvent(DepsCheckerEvent.dotnetAlreadyInstalled, "error");

// Assert
chai.assert.equal(eventName, DepsCheckerEvent.dotnetAlreadyInstalled);
});
it("sendSystemErrorEvent", () => {
// Arrange
let eventName = "";
const telemetryReporter = {
sendTelemetryErrorEvent(_eventName: string) {
eventName = _eventName;
},
} as any as TelemetryReporter;

// Act
const adapter = new CoreDepsTelemetryAdapter(telemetryReporter);
adapter.sendUserErrorEvent(DepsCheckerEvent.dotnetAlreadyInstalled, "error");

// Assert
chai.assert.equal(eventName, DepsCheckerEvent.dotnetAlreadyInstalled);
});
});
6 changes: 5 additions & 1 deletion packages/server/src/apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
TokenRequest,
Void,
} from "@microsoft/teamsfx-api";
import { VersionCheckRes } from "@microsoft/teamsfx-core";
import { DependencyStatus, TestToolInstallOptions, VersionCheckRes } from "@microsoft/teamsfx-core";
import {
CancellationToken,
NotificationType2,
Expand Down Expand Up @@ -179,6 +179,10 @@ export interface IServerConnection {
inputs: Inputs,
token: CancellationToken
) => Promise<Result<ApiOperation[], FxError>>;
checkAndInstallTestTool: (
options: TestToolInstallOptions & { correlationId: string },
token: CancellationToken
) => Promise<Result<DependencyStatus, FxError>>;
}

/**
Expand Down
Loading

0 comments on commit 9a338f7

Please sign in to comment.