diff --git a/AISKU/Tests/Perf/src/AISKUPerf.Tests.ts b/AISKU/Tests/Perf/src/AISKUPerf.Tests.ts new file mode 100644 index 000000000..d51879bbc --- /dev/null +++ b/AISKU/Tests/Perf/src/AISKUPerf.Tests.ts @@ -0,0 +1,361 @@ +import { AITestClass, Assert } from "@microsoft/ai-test-framework"; +import {AppInsightsInitPerfTestClass} from "./AISKUPerf"; + +function isNullOrUndefined(value: any): boolean { + return value === undefined || value === null; +} + +function createTrackEvent(eventName: string, type: string = "EventData", fieldNum?: number) { + let number = fieldNum? fieldNum: Math.random() * 10 + 10; + let fields = {}; + for (let i=1; i <= number; i++) { + let field = "field" + i; + fields[field] = "value" + i; + } + return {name: eventName, properties: fields, baseType: type }; +} + +function sendEventWithCollectorUrl(event: any) { + let oneDS = window["oneDS"]; + const generatedUrl = oneDS.getCollectorUrlGenerator(event); + let collectorUrlScript = document.createElement("script"); + collectorUrlScript.setAttribute("id", "collectorUrl"); + collectorUrlScript.src=generatedUrl; + document.head.appendChild(collectorUrlScript); + collectorUrlScript.onload=collectorUrlScript["onreadystatechange"] = function() { + Assert.ok(true,"telemetry sent"); + } +} + +function pushPerfEvents(event: any, AISKUPerfTest: any): void { + if (!AISKUPerfTest.doFlush) { + AISKUPerfTest.perfEventsBuffer.push(event); + } + else { + AISKUPerfTest.perfEventWaitBuffer.push(event); + } +} + +function flush(AISKUPerfTest: any): void { + AISKUPerfTest.doFlush = true; + console.log("flush " + AISKUPerfTest.perfEventsBuffer.length +" events"); + AISKUPerfTest.perfEventsBuffer.forEach((event) => { + if (event.baseData) {sendEventWithCollectorUrl(event);} + }) + AISKUPerfTest.perfEventsBuffer = AISKUPerfTest.perfEventWaitBuffer.slice(0); + AISKUPerfTest.doFlush = false; + AISKUPerfTest.perfEventWaitBuffer = []; + Assert.ok(true, "flush triggered"); +} + +const TENANT_KEY = "2252db2e5e344635a36c5f1c04b3902c-eaced1c8-a046-4e8d-8fa8-c1ecf2077a5d-7256" +function createPerfEvent(AISKUInitPerf: any, name: string, value: number, isProcessTime: boolean, msg?: string): void { + if (isNullOrUndefined(value) || value < 0 || isNullOrUndefined(AISKUInitPerf)) return; + let metricVal = isProcessTime? "ProcessTime":"UsedJSHeapSize"; + let unit = isProcessTime? "ms":"KB"; + let event = { + name: "SDKPerfTest", + iKey: TENANT_KEY, + ver: "4.0", + ext: {}, + baseData: { + testName: name, + sku:AISKUInitPerf.skuName, + version: AISKUInitPerf.version, + unitOfMeasure: unit, + metric: metricVal, + value: value + } + }; + + pushPerfEvents(event, AISKUInitPerf); + let message = msg? msg :`perfEvent: ${event.baseData.testName} ${event.baseData.value}${event.baseData.unitOfMeasure} added`; + console.log(message); + Assert.ok(true, message); +} + +function parseBatchSendEvent(perfEvent: any, AISKUInitPerf: any, memoryUsageMarks: any): void { + let curMemory = performance["memory"]?.usedJSHeapSize; + let memoryMarks = Object.keys(memoryUsageMarks); + var index = ""; + + if (memoryMarks && memoryMarks.length && curMemory !== undefined) { + index = memoryMarks[memoryMarks.length-1]; + createPerfEvent(AISKUInitPerf, perfEvent.name+index, perfEvent?.exTime, true); + } +} + +function parseTrackEvent(perfEvent: any, AISKUInitPerf: any): void { + let payloadItem = perfEvent["payload"]?.item; + let baseType = null; + if (perfEvent["time"] && payloadItem) { + baseType = payloadItem["baseType"]; + } + // skip PageviewPerformanceData && MessageData + if (isNullOrUndefined(baseType) || baseType == "PageviewPerformanceData" || baseType == "MessageData") return; + + createPerfEvent(AISKUInitPerf, `Track${baseType}`, perfEvent?.time, true); + var childEvts = perfEvent?.childEvts; + while (childEvts?.length) { + let curEvt = childEvts[0]; + let name = `${baseType}-${curEvt.name}`; + let value = curEvt?.exTime !== undefined ? curEvt.exTime:curEvt.time; + createPerfEvent(AISKUInitPerf, name, value, true); + childEvts = curEvt?.childEvts; + } +} + +function parseAppInsightsPerfEvent(perfEvent: any, AISKUInitPerf: any, memoryUsageMarks: any): void { + let perfEventName = perfEvent?.name; + switch (perfEventName) { + case "AISKU.flush": + parseBatchSendEvent(perfEvent, AISKUInitPerf, memoryUsageMarks); + break; + case "AppInsightsCore:track": + parseTrackEvent(perfEvent, AISKUInitPerf); + break; + } +} + +function addSnippetLoadingTimeEvent(AISKUPerfTest: any, endtime: number): void { + let rawTime = endtime - AISKUPerfTest.snippetStartTime; + var duration = Math.round(rawTime*1000)/1000; + createPerfEvent(AISKUPerfTest, "SDKInit", duration, true, `AppInsightsInit-Init: Script LoadingTime: ${duration} ms added`); +} + + +export class AISKUPerf extends AITestClass { + public AISKUPerfTest: AppInsightsInitPerfTestClass; + public perfMgr: any; + public doPerf: any; + public appInsights: any; + public initialMemoryUsage: number = 0; + public batchStartTimeMarks: any; + public memoryUsageMarks: any; + public perfMgrSrc: any = null; + + public testInitialize() { + try { + this.AISKUPerfTest = new AppInsightsInitPerfTestClass(); + this.AISKUPerfTest.loadScriptOnInit = () => {return this._loadScriptOnInit();} + Assert.ok(window["oneDS"], "oneDS exists"); + Assert.ok(window["Microsoft"]?.ApplicationInsights?.PerfMarkMeasureManager, "perfMgr exists"); + Assert.ok(window["Microsoft"]?.ApplicationInsights?.doPerf, "doPerf exists"); + this.perfMgr = window["Microsoft"].ApplicationInsights.PerfMarkMeasureManager; + this.doPerf = window["Microsoft"].ApplicationInsights.doPerf; + window["appInsightsInitPerftest"] = this.AISKUPerfTest; + this.memoryUsageMarks = {}; + this.batchStartTimeMarks = {}; + + } catch (e) { + console.error("Failed to initialize AISKUPerf Tests", e); + } + } + + public testCleanup() { + } + + public registerTests() { + this.addPerfTest(); + } + + constructor() { + super("AISKUPerfTest"); + } + + public addPerfTest(): void { + this.testCaseAsync({ + name: "AppInsights AISKU perf Test", + stepDelay: 10000, + steps: [() => { + Assert.ok(window["appInsightsInitPerftest"], "global appInsightsInitPerftest exists"); + Assert.ok(window["oneDS"], "oneDS exists"); + Assert.ok(this.perfMgr, "perfMgr exists"); + Assert.ok(this.doPerf, "doPerf exists"); + console.log(this.AISKUPerfTest.version); + + try { + if (!this.AISKUPerfTest.hasPerfMgr) { + this.perfMgrSrc = new this.perfMgr({ + useMarks: true, + useEndMarks: true, + uniqueNames: true + }, + { + perfEvent: (perfEvent) => { + parseAppInsightsPerfEvent(perfEvent,this.AISKUPerfTest, this.memoryUsageMarks); + } + }); + } + this.initialMemoryUsage = performance["memory"]?.usedJSHeapSize; + this._loadSnippet(); + } catch (e) { + Assert.ok(false, "load snippet error: " + e); + } + + }].concat(() => { + Assert.ok(true, "test version: " + this.AISKUPerfTest.version); + }) + }); + } + + protected _loadScriptOnInit(): void { + var snippetLoadingEndTime = performance.now(); + this._addMemoryPerfEvent(this.initialMemoryUsage); + Assert.ok(true,"AppInsights script loaded"); + + addSnippetLoadingTimeEvent(this.AISKUPerfTest,snippetLoadingEndTime); + + let appInsights = window["appInsights"]; + this.appInsights = appInsights; + + try { + let notificationManager = this.appInsights.core["_notificationManager"] ||this.appInsights.core?.getNotifyMgr(); + if (notificationManager) { + notificationManager.addNotificationListener({ + perfEvent: (perfEvent: any) => { + parseAppInsightsPerfEvent(perfEvent,this.AISKUPerfTest, this.memoryUsageMarks); + }}); + } + + } catch (e) { + console.error(e); + } + + this.addPerfEvents(); + + setTimeout(() => { + flush(this.AISKUPerfTest); + }, 9000); + } + + protected _loadSnippet():void { + this.initialMemoryUsage = performance["memory"]?.usedJSHeapSize; + var tag = document.createElement("script"); + tag.innerText = this.getSnippet(); + this.AISKUPerfTest.snippetStartTime = performance.now(); + document.getElementsByTagName("head")[0].appendChild(tag); + } + + public getSnippet(): string { + return ` + loadSdkUsingRequire({ + src: "https://js.monitor.azure.com/scripts/b/ai.${this.AISKUPerfTest.version}.min.js?${this.AISKUPerfTest.testPostfix}", + onInit: function (appInsights) { + console.log("snippet loaded"); + appInsightsInitPerftest.loadScriptOnInit(); + }, + cfg: { + instrumentationKey: "key", + enablePerfMgr: true, + maxBatchSizeInBytes: 1000000, + maxBatchInterval: 30000000, + extensionConfig: {} + } + });`; + } + + public addPerfEvents() { + this._addMemoryPerfEvent(this.initialMemoryUsage,"Configuration"); + this._trackSingleEvent(); + + setTimeout(() =>{ + this._trackBatchEvents(10); + setTimeout(() =>{ + this._trackBatchEvents(30); + setTimeout(() =>{ + this._trackBatchEvents(50); + setTimeout(() =>{ + this._trackBatchEvents(80); + setTimeout(() =>{ + this._trackBatchEvents(100); + },1000); + },1000); + },1000); + },1000); + },1000); + } + + public getScriptSrc(): string { + return `https://js.monitor.azure.com/scripts/b/ai.${this.AISKUPerfTest.version}.min.js?${this.AISKUPerfTest.testPostfix}`; + } + + private _addMemoryPerfEvent(initialMemoryUsage: number, metric?: string): void { + let curMemory = performance["memory"]?.usedJSHeapSize; + if (isNullOrUndefined(initialMemoryUsage) || initialMemoryUsage < 0) return; + + + if (!isNullOrUndefined(curMemory)) { + let metricName = metric? metric:"SDKInit"; + var memoryUsed = Math.round((curMemory - initialMemoryUsage) / 1000); + createPerfEvent(this.AISKUPerfTest, metricName, memoryUsed, false, `AppInsightsInit-Init: ${metricName} UsedJSHeapSize ${memoryUsed} KB added`); + } + } + + protected _trackSingleEvent() { + if (isNullOrUndefined(this.appInsights)) return; + + var event = createTrackEvent("Track","EventData",20); + var curMemeory = performance["memory"]?.usedJSHeapSize; + if (! isNullOrUndefined(curMemeory)) { + this.memoryUsageMarks["Track"] = curMemeory; + } + + try { + if (this.AISKUPerfTest.hasPerfMgr) { + this.appInsights.trackEvent(event); + } else { + this.doPerf(this.perfMgrSrc, () => "AppInsightsCore:track", (perfEvent) => { + this.appInsights.trackEvent(event); + // A mark has been added to window.performance called 'tst.mark.AppInsightsCore:track' + },()=> ({item: event})); + } + this.appInsights.flush(false); + } catch (e) { + console.error(e); + } + } + + protected _trackBatchEvents(number?: number) { + var curTime = performance.now(); + if (isNullOrUndefined(this.appInsights)) return; + + var eventNumber = number && number > 0? number:100; + var eventName = "BatchSend" + number; + this.batchStartTimeMarks[eventName] = curTime; + var event = createTrackEvent("batch","Message",20); + + var curMemory = performance["memory"]?.usedJSHeapSize; + if (!isNullOrUndefined(curMemory)) { + this.memoryUsageMarks[eventName] = curMemory; + } + + let beforeCreateBatch = performance.now(); + try { + for (let i = 0; i < eventNumber; i++) { + this.appInsights.trackTrace(event); + } + let afterCreateBatch = performance.now(); + let afterMemoUage = performance["memory"]?.usedJSHeapSize; + let val = Math.round((afterMemoUage-curMemory)/1000); + val = val < 0? 0:val; + let msg = `${eventName} Memory Usage: ${val}KB`; + createPerfEvent(this.AISKUPerfTest,eventName,val,false,msg); + let duration = Math.round((afterCreateBatch-beforeCreateBatch)*1000)/1000; + let createBatchName = `CreateBatch${eventNumber}` + let createBatchMsg = `${createBatchName} time: ${duration}ms`; + createPerfEvent(this.AISKUPerfTest,createBatchName,duration,true,createBatchMsg); + if (this.AISKUPerfTest.hasPerfMgr) { + this.appInsights.flush(false); + } else { + this.doPerf(this.perfMgrSrc, () => "AISKU.flush", (perfEvent) => { + this.appInsights.flush(false); + // A mark has been added to window.performance called 'tst.mark.AISKU.flush' + },()=> ({item: event})); + } + } catch (e) { + console.error(e); + } + + } +} diff --git a/AISKU/Tests/Perf/src/AISKUPerf.ts b/AISKU/Tests/Perf/src/AISKUPerf.ts new file mode 100644 index 000000000..b33b99eb5 --- /dev/null +++ b/AISKU/Tests/Perf/src/AISKUPerf.ts @@ -0,0 +1,43 @@ + +export class AppInsightsInitPerfTestClass { + + public version: string; + public perfEventsBuffer: any[]; + public perfEventWaitBuffer: any[]; + public testPostfix: string; + public INTERNAL_TENANT_KEY: string = "INTERNAL_TENANT_KEY"; + public doFlush: boolean; + public snippetStartTime: number; + public skuName: string = "AppInsights"; + public loadScriptOnInit: () => void; + public flush: () => void; + public sku: any = null; + public hasPerfMgr: boolean; + + constructor(ver?: string) { + /** + * Default current version is 2.7.1 + * should update version after new release + * version with doperf(): after 2.5.6 + * */ + var defaultVer = "2.7.1"; + this.version = ver? ver:this._getQueryParameterVersion(defaultVer); + this.perfEventsBuffer = []; + this.perfEventWaitBuffer = []; + this.testPostfix = Math.random().toString(36).slice(6); + this.doFlush = false; + this.snippetStartTime = 0; + this.hasPerfMgr = this.version <= "2.5.6"? false:true; + } + + protected _getQueryParameterVersion(defaultVer: string): string { + var version = defaultVer; + var location = window.location.search; + var queryParameter = new URLSearchParams(location); + let queryVer = queryParameter.get("version"); + if (queryVer && queryVer.length > 0) { version = queryVer;} + return version; + } +} + + diff --git a/AISKU/Tests/Perf/src/aiskuperftests.ts b/AISKU/Tests/Perf/src/aiskuperftests.ts new file mode 100644 index 000000000..e9b4b69b1 --- /dev/null +++ b/AISKU/Tests/Perf/src/aiskuperftests.ts @@ -0,0 +1,7 @@ +import { AISKUPerf } from "./AISKUPerf.Tests"; +//Do not remove this import, it will be used in browser. +import { Snippet } from "../../../src/Initialization"; + +export function runTests() { + new AISKUPerf().registerTests(); +} \ No newline at end of file diff --git a/AISKU/Tests/PerfTests.html b/AISKU/Tests/PerfTests.html new file mode 100644 index 000000000..40d5cb3d5 --- /dev/null +++ b/AISKU/Tests/PerfTests.html @@ -0,0 +1,86 @@ + + + + + + + Tests for Application Insights JavaScript AISKU + + + + + + + + + + + + + + + +
+
+
+ + + \ No newline at end of file diff --git a/AISKU/Tests/Selenium/appinsights-sdk.tests.ts b/AISKU/Tests/Selenium/appinsights-sdk.tests.ts deleted file mode 100644 index db6f24552..000000000 --- a/AISKU/Tests/Selenium/appinsights-sdk.tests.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { AISKUSizeCheck } from "../AISKUSize.Tests"; -import { ApplicationInsightsTests } from '../applicationinsights.e2e.tests'; -import { ApplicationInsightsFetchTests } from '../applicationinsights.e2e.fetch.tests'; -import { SanitizerE2ETests } from '../sanitizer.e2e.tests'; -import { ValidateE2ETests } from '../validate.e2e.tests'; -import { SenderE2ETests } from '../sender.e2e.tests'; -import { ApplicationInsightsDeprecatedTests } from '../ApplicationInsightsDeprecatedTests'; -import { SnippetLegacyInitializationTests } from '../SnippetLegacyInitialization.Tests'; -import { SnippetInitializationTests } from '../SnippetInitialization.Tests'; - - -new AISKUSizeCheck().registerTests(); -new ApplicationInsightsTests().registerTests(); -new ApplicationInsightsFetchTests().registerTests(); -new ApplicationInsightsDeprecatedTests().registerTests(); -new SanitizerE2ETests().registerTests(); -new ValidateE2ETests().registerTests(); -new SenderE2ETests().registerTests(); -new SnippetLegacyInitializationTests(false).registerTests(); -new SnippetInitializationTests(false).registerTests(); -new SnippetLegacyInitializationTests(true).registerTests(); -new SnippetInitializationTests(true).registerTests(); diff --git a/AISKU/Tests/TelemetryValidation/CommonValidator.ts b/AISKU/Tests/TelemetryValidation/CommonValidator.ts deleted file mode 100644 index 8ae09207c..000000000 --- a/AISKU/Tests/TelemetryValidation/CommonValidator.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ITypeValidator } from './ITypeValidator'; -import { IEnvelope } from '@microsoft/applicationinsights-common'; - -export class CommonValidator implements ITypeValidator { - - static CommonValidator = new CommonValidator(); - - public Validate(item: IEnvelope, baseType: string): boolean { - // verify item has data, iKey, name, tags, and time fields - if (!item.data || !item.iKey || !item.name || !item.tags || !item.time) { - return false; - }; - - if (item.data.baseData.ver !== 2) { - return false; - } - - // verify item.data has baseType field - if (!item.data.baseType) { - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/AISKU/Tests/TelemetryValidation/EventValidator.ts b/AISKU/Tests/TelemetryValidation/EventValidator.ts deleted file mode 100644 index 09e2681d1..000000000 --- a/AISKU/Tests/TelemetryValidation/EventValidator.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ITypeValidator } from './ITypeValidator'; -import { IEnvelope } from '@microsoft/applicationinsights-common'; -import { CommonValidator } from './CommonValidator'; - -export class EventValidator implements ITypeValidator { - - static EventValidator = new EventValidator(); - - public Validate(item: IEnvelope, baseType: string): boolean { - // verify item passes CommonValidator - if (!CommonValidator.CommonValidator.Validate(item, baseType)) { - return false; - } - - // verify item has ver, name, properties, and measurement fields - if (!item.data.baseData || - !item.data.baseData.ver || - !item.data.baseData.name || - !item.data.baseData.properties || - !item.data.baseData.measurements) { - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/AISKU/Tests/TelemetryValidation/ExceptionValidator.ts b/AISKU/Tests/TelemetryValidation/ExceptionValidator.ts deleted file mode 100644 index bb11a5585..000000000 --- a/AISKU/Tests/TelemetryValidation/ExceptionValidator.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { ITypeValidator } from './ITypeValidator'; -import { IEnvelope } from '@microsoft/applicationinsights-common'; -import { CommonValidator } from './CommonValidator'; - -export class ExceptionValidator implements ITypeValidator { - static ExceptionValidator = new ExceptionValidator(); - - private static _validateExceptions(exceptions: any[]): boolean { - // verify exceptions has typeName, message, hasFullStack, stack, parsedStack fields - if (!exceptions[0].typeName || - !exceptions[0].message || - !("hasFullStack" in exceptions[0]) || - !exceptions[0].stack || - !("parsedStack" in exceptions[0])) { - return false; - } - - return true; - } - - Validate(item: IEnvelope, baseType: string): boolean { - // verify item passes CommonValidator - if (!CommonValidator.CommonValidator.Validate(item, baseType)) { - return false; - } - - // verify item has ver and exceptions fields - if (!item.data.baseData || - !item.data.baseData.ver || - !item.data.baseData.exceptions) { - return false; - } - - // Exception.ver should be a number for breeze channel, but a string in CS4.0 - if (item.data.baseData.ver !== 2) { - return false; // not a valid breeze exception - } - - if (!ExceptionValidator._validateExceptions(item.data.baseData.exceptions)) { - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/AISKU/Tests/TelemetryValidation/ITypeValidator.ts b/AISKU/Tests/TelemetryValidation/ITypeValidator.ts deleted file mode 100644 index c5785585c..000000000 --- a/AISKU/Tests/TelemetryValidation/ITypeValidator.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { IEnvelope } from '@microsoft/applicationinsights-common'; - -export interface ITypeValidator { - Validate(item: IEnvelope, baseType?: string): boolean; -} \ No newline at end of file diff --git a/AISKU/Tests/TelemetryValidation/MetricValidator.ts b/AISKU/Tests/TelemetryValidation/MetricValidator.ts deleted file mode 100644 index 3ec876aee..000000000 --- a/AISKU/Tests/TelemetryValidation/MetricValidator.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ITypeValidator } from './ITypeValidator'; -import { IEnvelope } from '@microsoft/applicationinsights-common'; -import { CommonValidator } from './CommonValidator'; - -export class MetricValidator implements ITypeValidator { - static MetricValidator = new MetricValidator(); - - Validate(item: IEnvelope, baseType: string): boolean { - // verify item passes CommonValidator - if (!CommonValidator.CommonValidator.Validate(item, baseType)) { - return false; - } - - // verify item has ver, metrics, and properties fields - if (!item.data.baseData || - !item.data.baseData.ver || - !item.data.baseData.metrics || - !item.data.baseData.properties) { - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/AISKU/Tests/TelemetryValidation/PageViewPerformanceValidator.ts b/AISKU/Tests/TelemetryValidation/PageViewPerformanceValidator.ts deleted file mode 100644 index 470452704..000000000 --- a/AISKU/Tests/TelemetryValidation/PageViewPerformanceValidator.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ITypeValidator } from './ITypeValidator'; -import { IEnvelope } from '@microsoft/applicationinsights-common'; -import { CommonValidator } from './CommonValidator'; - -export class PageViewPerformanceValidator implements ITypeValidator { - static PageViewPerformanceValidator = new PageViewPerformanceValidator(); - - public Validate(item: IEnvelope, baseType: string): boolean { - // verify item passes CommonValidator - if (!CommonValidator.CommonValidator.Validate(item, baseType)) { - return false; - } - - // verify item has ver, url, perfTotal, name, duration, networkConnect, sentRequest, receivedResponse, and domProcessing fields - if (!item.data.baseData || - !item.data.baseData.ver || - !item.data.baseData.url || - !item.data.baseData.perfTotal || - !item.data.baseData.name || - !item.data.baseData.duration || - !item.data.baseData.networkConnect || - !item.data.baseData.sentRequest || - !item.data.baseData.receivedResponse || - !item.data.baseData.domProcessing) { - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/AISKU/Tests/TelemetryValidation/PageViewValidator.ts b/AISKU/Tests/TelemetryValidation/PageViewValidator.ts deleted file mode 100644 index 94fbb0021..000000000 --- a/AISKU/Tests/TelemetryValidation/PageViewValidator.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ITypeValidator } from './ITypeValidator'; -import { IEnvelope } from '@microsoft/applicationinsights-common'; -import { CommonValidator } from './CommonValidator'; - -export class PageViewValidator implements ITypeValidator { - static PageViewValidator = new PageViewValidator(); - - public Validate(item: IEnvelope, baseType: string): boolean { - // verify item passes CommonValidator - if (!CommonValidator.CommonValidator.Validate(item, baseType)) { - return false; - } - - // verify item has ver, url, name, duration, id, properties, and measurements fields - if (!item.data.baseData || - !item.data.baseData.ver || - !item.data.baseData.url || - !item.data.baseData.name || - !item.data.baseData.duration || - !item.data.baseData.id || - !item.data.baseData.properties || - !item.data.baseData.measurements) { - return false; - } - return true; - } -} \ No newline at end of file diff --git a/AISKU/Tests/TelemetryValidation/RemoteDepdencyValidator.ts b/AISKU/Tests/TelemetryValidation/RemoteDepdencyValidator.ts deleted file mode 100644 index 524a2ebc0..000000000 --- a/AISKU/Tests/TelemetryValidation/RemoteDepdencyValidator.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ITypeValidator } from './ITypeValidator'; -import { IEnvelope } from '@microsoft/applicationinsights-common'; -import { CommonValidator } from './CommonValidator'; - -export class RemoteDepdencyValidator implements ITypeValidator { - static RemoteDepdencyValidator = new RemoteDepdencyValidator(); - - public Validate(item: IEnvelope, baseType: string): boolean { - // verify item passes CommonValidator - if (!CommonValidator.CommonValidator.Validate(item, baseType)) { - return false; - } - - // verify item has ver, name, id, resultCode, duration, data, target, type, properties, and measurement fields - if (!item.data.baseData || - !item.data.baseData.ver || - !item.data.baseData.name || - !item.data.baseData.id || - !item.data.baseData.resultCode || - !item.data.baseData.duration || - !item.data.baseData.data || - !item.data.baseData.target || - !item.data.baseData.type || - !item.data.baseData.properties || - !item.data.baseData.measurements) { - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/AISKU/Tests/TelemetryValidation/TraceValidator.ts b/AISKU/Tests/TelemetryValidation/TraceValidator.ts deleted file mode 100644 index 35c93d59b..000000000 --- a/AISKU/Tests/TelemetryValidation/TraceValidator.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ITypeValidator } from './ITypeValidator'; -import { IEnvelope } from '@microsoft/applicationinsights-common'; -import { CommonValidator } from './CommonValidator'; - -export class TraceValidator implements ITypeValidator { - static TraceValidator = new TraceValidator(); - - public Validate(item: IEnvelope, baseType: string): boolean { - // verify item passes CommonValidator - if (!CommonValidator.CommonValidator.Validate(item, baseType)) { - return false; - } - - // verify item has ver, message, and properties fields - if (!item.data.baseData || - !item.data.baseData.ver || - !item.data.baseData.message || - !item.data.baseData.properties) { - return false; - } - return true; - } -} \ No newline at end of file diff --git a/AISKU/Tests/TestFramework/Assert.ts b/AISKU/Tests/TestFramework/Assert.ts deleted file mode 100644 index 16d536f23..000000000 --- a/AISKU/Tests/TestFramework/Assert.ts +++ /dev/null @@ -1,147 +0,0 @@ -/// - -/** - * Wrapper around QUnit asserts. This class has two purposes: - * - Make Assertion methods easy to discover. - * - Make them consistent with XUnit assertions in the order of the actual and expected parameter values. - */ -export class Assert { - /** - * A deep recursive comparison assertion, working on primitive types, arrays, objects, - * regular expressions, dates and functions. - * - * The deepEqual() assertion can be used just like equal() when comparing the value of - * objects, such that { key: value } is equal to { key: value }. For non-scalar values, - * identity will be disregarded by deepEqual. - * - * @param expected Known comparison value - * @param actual Object or Expression being tested - * @param message A short description of the assertion - */ - public static deepEqual(expected: any, actual: any, message?: string): any { - return deepEqual(actual, expected, message); - } - - /** - * A non-strict comparison assertion, roughly equivalent to JUnit assertEquals. - * - * The equal assertion uses the simple comparison operator (==) to compare the actual - * and expected arguments. When they are equal, the assertion passes: any; otherwise, it fails. - * When it fails, both actual and expected values are displayed in the test result, - * in addition to a given message. - * - * @param expected Known comparison value - * @param actual Expression being tested - * @param message A short description of the assertion - */ - public static equal(expected: any, actual: any, message?: string): any { - return equal(actual, expected, message); - } - - /** - * An inverted deep recursive comparison assertion, working on primitive types, - * arrays, objects, regular expressions, dates and functions. - * - * The notDeepEqual() assertion can be used just like equal() when comparing the - * value of objects, such that { key: value } is equal to { key: value }. For non-scalar - * values, identity will be disregarded by notDeepEqual. - * - * @param expected Known comparison value - * @param actual Object or Expression being tested - * @param message A short description of the assertion - */ - public static notDeepEqual(expected: any, actual: any, message?: string): any { - return notDeepEqual(actual, expected, message); - } - - /** - * A non-strict comparison assertion, checking for inequality. - * - * The notEqual assertion uses the simple inverted comparison operator (!=) to compare - * the actual and expected arguments. When they aren't equal, the assertion passes: any; - * otherwise, it fails. When it fails, both actual and expected values are displayed - * in the test result, in addition to a given message. - * - * @param expected Known comparison value - * @param actual Expression being tested - * @param message A short description of the assertion - */ - public static notEqual(expected: any, actual: any, message?: string): any { - return notEqual(actual, expected, message); - } - - public static notPropEqual(expected: any, actual: any, message?: string): any { - return notPropEqual(actual, expected, message); - } - - public static propEqual(expected: any, actual: any, message?: string): any { - return propEqual(actual, expected, message); - } - - /** - * A non-strict comparison assertion, checking for inequality. - * - * The notStrictEqual assertion uses the strict inverted comparison operator (!==) - * to compare the actual and expected arguments. When they aren't equal, the assertion - * passes: any; otherwise, it fails. When it fails, both actual and expected values are - * displayed in the test result, in addition to a given message. - * - * @param expected Known comparison value - * @param actual Expression being tested - * @param message A short description of the assertion - */ - public static notStrictEqual(expected: any, actual: any, message?: string): any { - return notStrictEqual(actual, expected, message); - } - - /** - * A boolean assertion, equivalent to CommonJS's assert.ok() and JUnit's assertTrue(). - * Passes if the first argument is truthy. - * - * The most basic assertion in QUnit, ok() requires just one argument. If the argument - * evaluates to true, the assertion passes; otherwise, it fails. If a second message - * argument is provided, it will be displayed in place of the result. - * - * @param state Expression being tested - * @param message A short description of the assertion - */ - public static ok(state: any, message?: string): any { - return ok(state, message); - } - - /** - * A strict type and value comparison assertion. - * - * The strictEqual() assertion provides the most rigid comparison of type and value with - * the strict equality operator (===) - * - * @param expected Known comparison value - * @param actual Expression being tested - * @param message A short description of the assertion - */ - public static strictEqual(expected: any, actual: any, message?: string): any { - return strictEqual(actual, expected, message); - } - - /** - * Assertion to test if a callback throws an exception when run. - * - * When testing code that is expected to throw an exception based on a specific set of - * circumstances, use throws() to catch the error object for testing and comparison. - * - * @param block Function to execute - * @param expected Error Object to compare - * @param message A short description of the assertion - */ - public static throws(block: () => any, expected: any, message?: string): any; - - /** - * @param block Function to execute - * @param message A short description of the assertion - */ - public static throws(block: () => any, message?: string): any; - - public static throws(block: () => any, expected?: any, message?: string): any { - return throws(block, expected, message); - } -} \ No newline at end of file diff --git a/AISKU/Tests/TestFramework/ContractTestHelper.ts b/AISKU/Tests/TestFramework/ContractTestHelper.ts deleted file mode 100644 index 63f66d9f4..000000000 --- a/AISKU/Tests/TestFramework/ContractTestHelper.ts +++ /dev/null @@ -1,147 +0,0 @@ -/// - -import { TestClass } from "./TestClass"; - -export class ContractTestHelper extends TestClass { - - public name: string; - private initializer: () => Microsoft.ApplicationInsights.ISerializable; - - constructor(initializer: () => Microsoft.ApplicationInsights.ISerializable, name: string) { - super(); - - this.name = name; - this.initializer = initializer; - } - - /** Method called before the start of each test method */ - public testInitialize() { - } - - /** Method called after each test method has completed */ - public testCleanup() { - } - - public registerTests() { - const name = this.name + ": "; - this.testCase({ - name: name + "constructor does not throw errors", - test: () => { - this.getSubject(this.initializer, this.name); - } - }); - - this.testCase({ - name: name + "serialization does not throw errors", - test: () => { - const subject = this.getSubject(this.initializer, this.name); - this.serialize(subject, this.name); - } - }); - - this.testCase({ - name: name + "all required fields are constructed", - test: () => { - this.allRequiredFieldsAreConstructed(this.initializer, this.name); - } - }); - - this.testCase({ - name: name + "extra fields are removed upon serialization", - test: () => { - this.extraFieldsAreRemovedBySerializer(this.initializer, this.name); - } - }); - - this.testCase({ - name: this.name + "optional fields are not required by the back end", - test: () => { - this.optionalFieldsAreNotRequired(this.initializer, this.name); - } - }); - - this.testCase({ - name: this.name + "all fields are serialized if included", - test: () => { - this.allFieldsAreIncludedIfSpecified(this.initializer, this.name); - } - }); - } - - public checkSerializableObject(initializer: () => any, name: string) { - this.allRequiredFieldsAreConstructed(initializer, name); - this.extraFieldsAreRemovedBySerializer(initializer, name); - this.allFieldsAreIncludedIfSpecified(initializer, name); - } - - private allRequiredFieldsAreConstructed(initializer: () => any, name: string) { - const subject = this.getSubject(initializer, name); - for (const field in subject.aiDataContract) { - if (subject.aiDataContract[field] & Microsoft.ApplicationInsights.FieldType.Required) { - Assert.ok(subject[field] != null, "The required field '" + field + "' is constructed for: '" + name + "'"); - } - } - } - - private extraFieldsAreRemovedBySerializer(initializer: () => any, name: string) { - const subject = this.getSubject(initializer, name); - - const extra = "extra"; - subject[extra + 0] = extra; - subject[extra + 1] = extra; - subject[extra + 3] = extra; - - const serializedSubject = this.serialize(subject, name); - - for (const field in serializedSubject) { - Assert.ok(subject.aiDataContract[field] != null, "The field '" + field + "' exists in the contract for '" + name + "' and was serialized"); - } - } - - private optionalFieldsAreNotRequired(initializer: () => any, name: string) { - const subject = this.getSubject(this.initializer, this.name); - - for (const field in subject.aiDataContract) { - if (!subject.aiDataContract[field]) { - delete subject[field]; - } - } - } - - private allFieldsAreIncludedIfSpecified(initializer: () => any, name: string) { - const subject = this.getSubject(this.initializer, this.name); - - for (const field in subject.aiDataContract) { - subject[field] = field; - } - - const serializedSubject = this.serialize(subject, this.name); - - for (field in subject.aiDataContract) { - Assert.ok(serializedSubject[field] === field, "Field '" + field + "' was not serialized" + this.name); - } - - for (field in serializedSubject) { - Assert.ok(subject.aiDataContract[field] !== undefined, "Field '" + field + "' was included but is not specified in the contract " + this.name); - } - } - - private serialize(subject: Microsoft.ApplicationInsights.ISerializable, name: string) { - let serialized = ""; - - try { - serialized = Microsoft.ApplicationInsights.Serializer.serialize(subject); - } catch (e) { - Assert.ok(false, "Failed to serialize '" + name + "'\r\n" + e); - } - - return JSON.parse(serialized); - } - - private getSubject(construction: () => Microsoft.ApplicationInsights.ISerializable, name: string): any { - const subject = construction(); - Assert.ok(!!subject, "can construct " + name); - - return subject; - } -} \ No newline at end of file diff --git a/AISKU/Tests/TestFramework/PerformanceTestHelper.ts b/AISKU/Tests/TestFramework/PerformanceTestHelper.ts deleted file mode 100644 index 4ec002b1b..000000000 --- a/AISKU/Tests/TestFramework/PerformanceTestHelper.ts +++ /dev/null @@ -1,227 +0,0 @@ -/// -/// -/// -/// -interface IJSLitmus { - test: (name: string, f: Function) => void; - stop: () => void; - runAll: (e?: Event) => JQueryDeferred; - _tests: any[]; -} - -interface IPerfResult { - operationCount: number; - timeInMs: number; - name: string; - opsPerSec: number; - period: number; - date: number; - platform: string; - os: string; - oneHrDate: number; - friendlyDate: string; - group: string; - millisecondsPerOp: number; - microsecondsPerOp: number; - secondsPerOp: number; - browser: string; -} - -declare var JSLitmus: IJSLitmus; - -class PerformanceTestHelper extends TestClass { - - public testCount; - public appInsights; - public testProperties; - public testMeasurements; - public results: IPerfResult[]; - private isDone; - - constructor(timeout?: number) { - super(); - this.testCount = 0; - this.synchronouslyLoadJquery(); - this.results = []; - } - - public enqueueTest(name: string, action: () => void) { - JSLitmus.test(name, (count) => { - while (count--) { - action(); - } - }); - } - - public runTests() { - JSLitmus.runAll().done(() => this.onTestsComplete()); - } - - public onTestsComplete() { - const perfLogging = new Microsoft.ApplicationInsights.AppInsights({ - instrumentationKey: "1a6933ad-f260-447f-a2b0-e2233f6658eb", - url: "file:///C:/src/sdk/src/JavaScript/JavaScriptSDK.Tests//E2ETests/ai.js", - endpointUrl: "http://prodintdataforker.azurewebsites.net/dcservices?intKey=4d93aad0-cf1d-45b7-afc9-14f55504f6d5", - sessionRenewalMs: 30 * 60 * 1000, - sessionExpirationMs: 24 * 60 * 60 * 1000, - maxBatchSizeInBytes: 1000000, - maxBatchInterval: 0 - } as any); - - perfLogging.context._sender._sender = (payload) => { - const xhr = new sinon["xhr"].workingXHR(); - xhr.open("POST", perfLogging.config.endpointUrl, true); - xhr.setRequestHeader("Content-type", "application/json"); - xhr.send(payload); - } - - JSLitmus.stop(); - for (let i = 0; i < JSLitmus._tests.length; i++) { - const test = JSLitmus._tests[i]; - const opsPerSec = test.count / test.time; - Assert.ok(true, test.name + " operations per sec:" + opsPerSec); - - const timeInMs = test.time as number; - const date = +new Date; - const oneHr = 60 * 60 * 1000; - const oneHrDate = Math.floor(date / oneHr) * oneHr; - const friendlyDate = new Date(oneHrDate).toISOString(); - const platform = test.platform as string; - let browser = "internetExplorer"; - const name = test.name as string; - const group = name.split(".")[0]; - if (platform.toLowerCase().indexOf("chrome") >= 0) { - browser = "chrome"; - } else if (platform.toLowerCase().indexOf("firefox") >= 0) { - browser = "firefox"; - } else if (platform.toLowerCase().indexOf("safari") >= 0) { - browser = "safari"; - } - - const result: IPerfResult = { - name, - timeInMs, - operationCount: 1, - opsPerSec: 1 / (timeInMs / 1000), - period: 1, - date, - oneHrDate, - friendlyDate, - group, - platform, - browser, - os: test.os as string, - millisecondsPerOp: (timeInMs / 1), - microsecondsPerOp: (timeInMs / 1) * 1000, - secondsPerOp: (timeInMs / 1) / 1000 - }; - - perfLogging.trackMetric(result.name, opsPerSec); - const event = new Microsoft.ApplicationInsights.Telemetry.Event(result.name, opsPerSec, result); - const data = new Microsoft.ApplicationInsights.Telemetry.Common.Data( - Microsoft.ApplicationInsights.Telemetry.Event.dataType, event); - const envelope = new Microsoft.ApplicationInsights.Telemetry.Common.Envelope(data, Microsoft.ApplicationInsights.Telemetry.Event.envelopeType); - perfLogging.context.track(envelope); - - this.results.push(result); - } - - JSLitmus._tests.length = 0; - this.isDone = true; - this.testCleanup(); - } - - public onTimeout() { - if (!this.isDone) { - Assert.ok(false, "timeout reached"); - this.onTestsComplete(); - } - } - - /** Method called before the start of each test method */ - public testInitialize() { - this.useFakeServer = false; - sinon.fakeServer["restore"](); - - this.useFakeTimers = false; - this.clock.restore(); - - this.appInsights = new Microsoft.ApplicationInsights.AppInsights({ - instrumentationKey: "3e6a441c-b52b-4f39-8944-f81dd6c2dc46", - url: "file:///C:/src/sdk/src/JavaScript/JavaScriptSDK.Tests//E2ETests/ai.js", - endpointUrl: "https://dc.services.visualstudio.com/v2/track", - maxBatchInterval: 0 - } as any); - - this.appInsights.context._sender._sender = () => null; - this.testProperties = { p1: "val", p2: "val", p3: "val", p4: "val", p5: "val", p6: "val", p7: "val" }; - this.testMeasurements = { m1: 1, m2: 1, m3: 1, m4: 1, m5: 1, m6: 1, m7: 1, m8: 1, m9: 1 }; - } - - /** Method called after each test method has completed */ - public testCleanup() { - this.useFakeServer = true; - this.useFakeTimers = true; - const serializedPerfResults: string = window["perfResults"] || "[]"; - let perfResults: IPerfResult[] = (JSON.parse(serializedPerfResults)) as any; - perfResults = perfResults.concat(this.results); - window["perfResults"] = JSON.stringify(perfResults); - window["perfResultsCsv"] = this.toCsv(perfResults).csv; - window["perfResultsCsvHeaders"] = this.toCsv(perfResults).headers; - } - - private toCsv(array: any[]) { - let headers = ""; - if (array.length > 0) { - const names = []; - for (const name in array[0]) { - names.push(name); - } - - headers = names.join(","); - } - - const csv = []; - for (const i = 0; i < array.length; i++) { - const datum = array[i]; - const values = []; - for (let j = 0; j < names.length; j++) { - values.push(datum[names[j]]); - } - - csv.push(values.join(",")); - } - - return { headers, csv: csv.join("\r\n") }; - } - - /** - * Synchronously loads jquery - * we could regress the test suite and develop sublte jquery dependencies in the product code - * if jquery were added to all tests as it hides a lot of cross browser weirdness. However, - * for these tests it is useful to manipulate the dom to display performance results. - */ - private synchronouslyLoadJquery() { - if (!window["$"]) { - // get some kind of XMLHttpRequest - let xhrObj = false as any; - if (window["ActiveXObject"]) { - xhrObj = (new ActiveXObject("Microsoft.XMLHTTP") as any); - } else if (window["XMLHttpRequest"]) { - xhrObj = (new XMLHttpRequest() as any); - } else { - alert("Please upgrade your browser! Your browser does not support AJAX!"); - } - - // open and send a synchronous request - xhrObj.open('GET', "http://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.1/jquery.js", false); - xhrObj.send(''); - - // add the returned content to a newly created script tag - const script = document.createElement('script'); - script.type = "text/javascript"; - script.text = xhrObj.responseText; - document.getElementsByTagName('head')[0].appendChild(script); - } - } -} \ No newline at end of file diff --git a/AISKU/Tests/TestFramework/PollingAssert.ts b/AISKU/Tests/TestFramework/PollingAssert.ts deleted file mode 100644 index d9767748e..000000000 --- a/AISKU/Tests/TestFramework/PollingAssert.ts +++ /dev/null @@ -1,40 +0,0 @@ -/// -import { Assert } from "./Assert"; -import { TestClass } from "./TestClass"; - -export class PollingAssert { - /** - * Starts polling assertion function for a period of time after which it's considered failed. - * @param {() => boolean} assertionFunctionReturnsBoolean - funciton returning true if condition passes and false if condition fails. Assertion will be done on this function's result. - * @param {string} assertDescription - message shown with the assertion - * @param {number} timeoutSeconds - timeout in seconds after which assertion fails - * @param {number} pollIntervalMs - polling interval in milliseconds - * @returns {(nextTestStep) => void} callback which will be invoked by the TestClass - */ - public static createPollingAssert(assertionFunctionReturnsBoolean: () => boolean, assertDescription: string, timeoutSeconds: number = 30, pollIntervalMs: number = 500): (nextTestStep) => void { - const pollingAssert = (nextTestStep) => { - const timeout = new Date(new Date().getTime() + timeoutSeconds * 1000); - const polling = () => { - try { - if (assertionFunctionReturnsBoolean.apply(this)) { - Assert.ok(true, assertDescription + "[" + (TestClass.currentTestInfo ? TestClass.currentTestInfo.name : "") + "]"); - nextTestStep(); - } else if (timeout < new Date()) { - Assert.ok(false, "assert didn't succeed for " + timeout + " seconds: " + assertDescription + "[" + (TestClass.currentTestInfo ? TestClass.currentTestInfo.name : "") + "]"); - nextTestStep(); - } else { - TestClass.orgSetTimeout(polling, pollIntervalMs); - } - } catch (e) { - Assert.ok(true, "Polling exception - " + e); - TestClass.orgSetTimeout(polling, pollIntervalMs); - } - } - TestClass.orgSetTimeout(polling, pollIntervalMs); - } - - pollingAssert[TestClass.isPollingStepFlag] = true; - - return pollingAssert; - } -} diff --git a/AISKU/Tests/TestFramework/TestCase.ts b/AISKU/Tests/TestFramework/TestCase.ts deleted file mode 100644 index b9fdab24c..000000000 --- a/AISKU/Tests/TestFramework/TestCase.ts +++ /dev/null @@ -1,61 +0,0 @@ - -/** Defines a test case */ -export class TestCase { - /** Name to use for the test case */ - public name: string; - - useFakeServer?: boolean; - fakeServerAutoRespond?: boolean; - - useFakeTimers?: boolean; - - /** Test case method */ - public test: () => void|Promise; - - /** We expect the test to complete within this interval (defaults to 5 seconds) */ - public timeout?: number; - - /** Used for debugging, set this value to ignore the automatic timeout for tests that return a promise */ - public skipTimeout? : boolean; -} - -export const enum StepResult { - Abort = -1, - Complete = 0, - Repeat = 90, - Retry = 99 -} - -export interface ITestContext { - context: { [key: string]: any }; - retryCnt: number; - testDone: VoidFunction; // Consider that the test is complete -} - -/** Defines a test case */ -export interface TestCaseAsync { - /** Name to use for the test case */ - name: string; - - useFakeServer?: boolean; - fakeServerAutoRespond?: boolean; - - useFakeTimers?: boolean; - - /** time to wait after pre before invoking post and calling start() */ - stepDelay: number; - - /** async steps */ - steps: Array<(testContext?: ITestContext) => StepResult|boolean|void>; - - /** - * Terminate and fail the test if it runs longer than this - */ - timeOut?: number; - - /** - * Flag which specifies that once all of the steps are completed the test case is completed. - * True by default - */ - autoComplete?: boolean; -} \ No newline at end of file diff --git a/AISKU/Tests/TestFramework/TestClass.ts b/AISKU/Tests/TestFramework/TestClass.ts deleted file mode 100644 index af67ede94..000000000 --- a/AISKU/Tests/TestFramework/TestClass.ts +++ /dev/null @@ -1,706 +0,0 @@ -/// -import { SinonSandbox, SinonSpy, SinonStub, SinonMock, SinonFakeXMLHttpRequest } from 'sinon'; -import * as sinon from 'sinon'; -import dynamicProto from '@microsoft/dynamicproto-js'; -import { ITestContext, StepResult, TestCase, TestCaseAsync } from './TestCase'; -import { Assert } from './Assert'; -import { CoreUtils, getGlobal } from '@microsoft/applicationinsights-core-js'; - -const stepRetryCnt = 'retryCnt'; - -export interface FakeXMLHttpRequest extends XMLHttpRequest { - url?: string; - method?: string; - requestHeaders?: any; - respond: (status: number, headers: any, body: string) => void; -} - -export class TestClass { - public static isPollingStepFlag = "isPollingStep"; - - /** The instance of the currently running suite. */ - public static currentTestClass: TestClass; - public static currentTestInfo: TestCase|TestCaseAsync; - public static orgSetTimeout: (handler: Function, timeout?: number) => number; - public static orgClearTimeout: (handle?: number) => void; - - /** Turns on/off sinon's fake implementation of XMLHttpRequest. On by default. */ - public sandboxConfig: any = {}; - - public static orgObjectDefineProperty = Object.defineProperty; - - protected _xhrRequests: FakeXMLHttpRequest[] = []; - protected _clock = null; - - /** Default value for whether to turns on/off sinon's fake implementation of XMLHttpRequest. On by default. */ - private _useFakeServer: boolean = true; - public fakeServerAutoRespond: boolean = false; - public isEmulatingEs3: boolean; - - private _moduleName: string; - - private _xhr; - private _xhrOrgSend; - private _orgNavigator: any; - private _beaconHooks = []; - private _dynProtoOpts: any = null; - - // Simulate an Es3 environment - private _orgObjectFuncs = null; - private _orgFetch = null; - - constructor(name?: string, emulateEs3?: boolean) { - this._moduleName = (emulateEs3 ? "(ES3) " : "") + name; - this.isEmulatingEs3 = emulateEs3 - QUnit.module(this._moduleName); - this.sandboxConfig.injectIntoThis = true; - this.sandboxConfig.injectInto = null; - this.sandboxConfig.properties = ["spy", "stub", "mock", "sandbox"]; - } - - get useFakeServer(): boolean { - return this._useFakeServer; - } - - set useFakeServer(value: boolean) { - this._useFakeServer = value; - if (!value) { - this._unhookXhr(); - } else if (value && TestClass.currentTestInfo) { - this._hookXhr(); - } - } - - /** Method called before the start of each test method */ - public testInitialize() { - } - - /** Method called after each test method has completed */ - public testCleanup() { - } - - /** Method in which test class intances should call this.testCase(...) to register each of this suite's tests. */ - public registerTests() { - } - - /** Register an async Javascript unit testcase. */ - public testCaseAsync(testInfo: TestCaseAsync) { - if (!testInfo.name) { - throw new Error("Must specify name in testInfo context in registerTestcase call"); - } - - if (isNaN(testInfo.stepDelay)) { - throw new Error("Must specify 'stepDelay' period between pre and post"); - } - - if (!testInfo.steps) { - throw new Error("Must specify 'steps' to take asynchronously"); - } - if (testInfo.autoComplete === undefined) { - testInfo.autoComplete = true; - } - - if (testInfo.autoComplete === undefined) { - testInfo.autoComplete = true; - } - - // Create a wrapper around the test method so we can do test initilization and cleanup. - const testMethod = (assert: any) => { - const done = assert.async(); - - // Save off the instance of the currently running suite. - TestClass.currentTestClass = this; - TestClass.currentTestInfo = testInfo; - - // Save the real clearTimeout (as _testStarting and enable sinon fake timers) - const orgClearTimeout = clearTimeout; - const orgSetTimeout = setTimeout; - - TestClass.orgSetTimeout = (handler: Function, timeout?: number) => { - return orgSetTimeout(handler, timeout); - }; - - TestClass.orgClearTimeout = (handler: number) => { - orgClearTimeout(handler); - }; - - let useFakeServer = testInfo.useFakeServer; - if (useFakeServer === undefined) { - useFakeServer = this.useFakeServer; - } - - if (useFakeServer) { - this._hookXhr(); - } - - if (testInfo.useFakeTimers) { - this._clock = sinon.useFakeTimers(); - } - - if (this.isEmulatingEs3) { - this._emulateEs3(); - } - - // Run the test. - try { - let self = this; - let testComplete = false; - let timeOutTimer = null; - let stepIndex = 0; - - const testDone = () => { - if (timeOutTimer) { - orgClearTimeout(timeOutTimer); - } - - testComplete = true; - // done is QUnit callback indicating the end of the test - self._testCompleted(); - done(); - }; - - let testContext: ITestContext = { - context: {}, - retryCnt: 0, - testDone: testDone - }; - - if (testInfo.timeOut !== undefined) { - timeOutTimer = orgSetTimeout(() => { - QUnit.assert.ok(false, "Test case timed out!"); - testComplete = true; - done(); - }, testInfo.timeOut); - } - - self._testStarting(); - - const steps = testInfo.steps; - const trigger = () => { - // The callback which activates the next test step. - let nextTestStepTrigger = () => { - orgSetTimeout(() => { - trigger(); - }, testInfo.stepDelay); - }; - - if (steps.length && !testComplete) { - const step = steps.shift(); - stepIndex++; - - // There 2 types of test steps - simple and polling. - // Upon completion of the simple test step the next test step will be called. - // In case of polling test step the next test step is passed to the polling test step, and - // it is responsibility of the polling test step to call the next test step. - try { - if (step) { - if (step[TestClass.isPollingStepFlag]) { - step.call(this, nextTestStepTrigger); - } else { - testContext.retryCnt = step[stepRetryCnt] || 0; - - let result = step.call(this, testContext); - if (result === StepResult.Retry || result === false) { - // The step requested itself to be retried - Assert.ok(false, 'Retrying Step - ' + stepIndex + ' - Attempt #' + testContext.retryCnt); - step[stepRetryCnt] = testContext.retryCnt + 1; - steps.unshift(step); - stepIndex--; - } else if (result === StepResult.Repeat) { - // The step requested itself to be repeated - steps.unshift(step); - stepIndex--; - } else if (result === StepResult.Abort) { - Assert.ok(false, 'Step aborted!'); - testDone(); - return; - } - - nextTestStepTrigger.call(this); - } - } - } catch (e) { - console.error("Failed: Unexpected Exception: " + e); - Assert.ok(false, e.toString()); - this._testCompleted(true); - // done is QUnit callback indicating the end of the test - done(); - - return; - } - } else if (!testComplete) { - if (testInfo.autoComplete) { - // done is QUnit callback indicating the end of the test - testDone(); - } else { - nextTestStepTrigger(); - } - } - }; - - trigger(); - } catch (ex) { - console.error("Failed: Unexpected Exception: " + ex); - Assert.ok(false, "Unexpected Exception: " + ex); - this._testCompleted(true); - - // done is QUnit callback indicating the end of the test - done(); - } - }; - - // Register the test with QUnit - QUnit.test((this.isEmulatingEs3 ? "(ES3) " : "") + testInfo.name + " - (Async)", testMethod); - } - - /** Register a Javascript unit testcase. */ - public testCase(testInfo: TestCase) { - if (!testInfo.name) { - throw new Error("Must specify name in testInfo context in registerTestcase call"); - } - - if (!testInfo.test) { - throw new Error("Must specify 'test' method in testInfo context in registerTestcase call"); - } - - // Create a wrapper around the test method so we can do test initilization and cleanup. - const testMethod = (assert) => { - // Treating all tests as async, so there is no issues with mixing them - let done = assert.async(); - - // Save off the instance of the currently running suite. - TestClass.currentTestClass = this; - TestClass.currentTestInfo = testInfo; - function _testFinished(failed?: boolean) { - TestClass.currentTestClass._testCompleted(failed); - done(); - } - - // Save the real clearTimeout (as _testStarting and enable sinon fake timers) - const orgClearTimeout = clearTimeout; - const orgSetTimeout = setTimeout; - - TestClass.orgSetTimeout = (handler: Function, timeout?: number) => { - return orgSetTimeout(handler, timeout); - }; - - TestClass.orgClearTimeout = (handler: number) => { - orgClearTimeout(handler); - }; - - let useFakeServer = testInfo.useFakeServer; - if (useFakeServer === undefined) { - useFakeServer = this.useFakeServer; - } - - if (useFakeServer) { - this._hookXhr(); - } - - if (testInfo.useFakeTimers) { - this._clock = sinon.useFakeTimers(); - } - - if (this.isEmulatingEs3) { - this._emulateEs3(); - } - - let failed = false; - - TestClass.orgSetTimeout = (handler:Function, timeout?:number) => { - return orgSetTimeout(handler, timeout); - } - - TestClass.orgClearTimeout = (handler:number) => { - orgClearTimeout(handler); - } - - // Run the test. - try { - this._testStarting(); - - let result = testInfo.test.call(this); - // Check if the result is present and looks like a Promise - if (result && result.then) { - let promiseTimeout = null; - if (testInfo.skipTimeout !== true) { - let timeout = testInfo.timeout || 5000; - promiseTimeout = setTimeout(() => { - QUnit.assert.ok(false, "Timeout: Aborting as the Promise returned from the test method did not resolve within " + timeout + " ms"); - _testFinished(true); - }, timeout); - } - result.then(() => { - promiseTimeout && clearTimeout(promiseTimeout); - _testFinished(); - }).catch((reason) => { - promiseTimeout && clearTimeout(promiseTimeout); - QUnit.assert.ok(false, "Returned Promise rejected: " + reason); - _testFinished(true); - }); - } else { - _testFinished(); - } - } catch (ex) { - console.error("Failed: Unexpected Exception: " + ex); - Assert.ok(false, "Unexpected Exception: " + ex); - _testFinished(true); - } - }; - - // Register the test with QUnit - QUnit.test((this.isEmulatingEs3 ? "(ES3) " : "") + testInfo.name, testMethod); - } - - protected _deleteAllCookies() { - var cookies = document.cookie.split(";"); - - for (var i = 0; i < cookies.length; i++) { - var cookie = cookies[i]; - var eqPos = cookie.indexOf("="); - var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; - document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"; - } - } - - protected _disableDynProtoBaseFuncs() { - let defOpts = dynamicProto['_dfOpts']; - if (defOpts) { - if (!this._dynProtoOpts) { - // Save the current settings so we can restore them - this._dynProtoOpts = {}; - Object.keys(defOpts).forEach((key) => { - this._dynProtoOpts[key] = defOpts[key]; - }); - } - - defOpts.useBaseInst = false; - } - } - - /** Called when the test is starting. */ - private _testStarting() { - let _self = this; - // Initialize the sandbox similar to what is done in sinon.js "test()" override. See note on class. - _self.sandbox = sinon.createSandbox(this.sandboxConfig); - - if (_self.isEmulatingEs3) { - // As we removed Object.define we need to temporarily restore this for each sandbox call - // tslint:disable-next-line:forin - for (var field in _self.sandbox) { - var value = _self.sandbox[field]; - if (CoreUtils.isFunction(value)) { - _self.sandbox[field] = (function(theValue) { - return function() { - var orgArguments = arguments; - let saveObjectProps = {}; - // Save and restore the Object properties/functions - if (_self._orgObjectFuncs) { - for (var name in _self._orgObjectFuncs) { - if (_self._orgObjectFuncs.hasOwnProperty(name) && _self._orgObjectFuncs[name] !== undefined) { - saveObjectProps[name] = Object[name]; - Object[name] = _self._orgObjectFuncs[name]; - } - } - } - - // Call the original sandbox function - let result = theValue.apply(this, orgArguments); - - // Restore the Object properties/functions - _self._restoreObject(saveObjectProps); - return result; - } - })(value); - } - } - } - - // Allow the derived class to perform test initialization. - this.testInitialize(); - } - - /** Called when the test is completed. */ - private _testCompleted(failed?: boolean) { - this._unhookXhr(); - - if (this._clock) { - this._clock.restore(); - this._clock = null; - } - - if (this._orgNavigator) { - this.setNavigator(this._orgNavigator); - this._orgNavigator = null; - } - - this._beaconHooks = []; - this._cleanupAllHooks(); - this._restoreEs3(); - - if (failed) { - // Just cleanup the sandbox since the test has already failed. - this.sandbox.restore(); - } - else { - // Verify the sandbox and restore. - (this.sandbox as any).verifyAndRestore(); - } - - this.testCleanup(); - - // Clear the instance of the currently running suite. - TestClass.currentTestClass = null; - TestClass.currentTestInfo = null; - } - - private _removeFuncHooks(fn:any) { - if (typeof fn === "function") { - let aiHook:any = fn["_aiHooks"]; - - if (aiHook && aiHook.h) { - aiHook.h = []; - } - } - } - - private _removeHooks(target:any) { - Object.keys(target).forEach(name => { - try { - this._removeFuncHooks(target[name]); - } catch (e) { - } - }); - } - - private _cleanupAllHooks() { - this._removeHooks(XMLHttpRequest.prototype); - this._removeHooks(XMLHttpRequest); - this._removeFuncHooks(window.fetch); - } - - private _restoreObject(objectProps: any) { - if (objectProps) { - // Restore the Object properties/functions - for (var name in objectProps) { - if (objectProps.hasOwnProperty(name) && objectProps[name] !== undefined) { - Object[name] = objectProps[name]; - } - } - } - } - - private _restoreEs3() { - this._restoreObject(this._orgObjectFuncs); - this._orgObjectFuncs = null; - - if (this._orgFetch) { - let global = getGlobal() as any; - global.fetch = this._orgFetch; - this._orgFetch = null; - } - } - - private _emulateEs3() { - const objectNames = [ "defineProperty", "defineProperties"]; - if (!this._orgObjectFuncs) { - this._orgObjectFuncs = {}; - for (var lp = 0; lp < objectNames.length; lp++) { - let name = objectNames[lp]; - this._orgObjectFuncs[name] = Object[name]; - Object[name] = null; - } - } - - if (!this._orgFetch) { - let global = getGlobal() as any; - this._orgFetch = global.fetch; - global.fetch = null; - } - - // Lets pretend to also be IE8 - this.setUserAgent("Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)"); - } - - private _unhookXhr() { - if (this._xhr) { - if (this._xhrOrgSend) { - this._xhr.prototype.send = this._xhrOrgSend; - this._xhrOrgSend = null; - } - - this._xhr.restore(); - this._xhr = null; - } - } - - private _hookXhr() { - let _self = this; - if (!_self._xhr) { - // useFake Server is being re-enabled while we are running a test so we need to re-fake it - _self._xhr = sinon.useFakeXMLHttpRequest(); - _self._xhrRequests = []; - _self._xhr.onCreate = (xhr: FakeXMLHttpRequest) => { - _self._xhrRequests.push(xhr); - } - - _self._xhrOrgSend = _self._xhr.prototype.send; - _self._xhr.prototype.send = function() { - let xhr = this; - let theArguments = arguments; - let autoRespond = _self.fakeServerAutoRespond; - if (TestClass.currentTestInfo && TestClass.currentTestInfo.fakeServerAutoRespond !== undefined) { - autoRespond = TestClass.currentTestInfo.fakeServerAutoRespond; - } - - if (autoRespond && xhr && xhr.url && xhr.method) { - TestClass.orgSetTimeout && TestClass.orgSetTimeout(() => { - if (TestClass.currentTestInfo && xhr && _self._xhrRequests && _self._xhrRequests.indexOf(xhr) !== -1) { - xhr.respond(200, {}, ""); - } - }, 5); - } - - if (_self._xhrOrgSend) { - _self._xhrOrgSend.apply(xhr, theArguments); - } - } - } - } - - /**** Sinon methods and properties ***/ - - // These methods and properties are injected by Sinon and will override the implementation here. - // These are here purely to make typescript happy. - public sandbox: SinonSandbox; - - /** Creates an anonymous function that records arguments, this value, exceptions and return values for all calls. */ - public spy(): SinonSpy; - /** Spies on the provided function */ - public spy(funcToWrap: Function): SinonSpy; - /** Creates a spy for object.methodName and replaces the original method with the spy. The spy acts exactly like the original method in all cases. The original method can be restored by calling object.methodName.restore(). The returned spy is the function object which replaced the original method. spy === object.method. */ - public spy(object: any, methodName: string, func?: Function): SinonSpy; - public spy(...args: any[]): SinonSpy { return null; } - - /** Creates an anonymous stub function. */ - public stub(): SinonStub; - /** Stubs all the object's methods. */ - public stub(object: any): SinonStub; - public stub(...args: any[]): SinonStub { return null; } - - /** Creates a mock for the provided object.Does not change the object, but returns a mock object to set expectations on the object's methods. */ - public mock(object: any): SinonMock { return null; } - - /**** end: Sinon methods and properties ***/ - - protected setUserAgent(userAgent: string) { - // Hook Send beacon which also mocks navigator - this.hookSendBeacon(null); - - TestClass.orgObjectDefineProperty(window.navigator, 'userAgent', - { - configurable: true, - get: function () { - return userAgent; - } - }); - } - - protected hookSendBeacon(cb: (url: string, data?: BodyInit | null) => void) { - if (!this._orgNavigator) { - let newNavigator = {}; - this._orgNavigator = window.navigator; - - newNavigator.sendBeacon = (url, body) => { - this._beaconHooks.forEach(element => { - element(url, body); - }); - return this._orgNavigator.sendBeacon(url, body); - }; - - // Just Blindly copy the properties over - // tslint:disable-next-line: forin - for (let name in navigator) { - if (!newNavigator.hasOwnProperty(name)) { - newNavigator[name] = navigator[name]; - if (!newNavigator.hasOwnProperty(name)) { - // if it couldn't be set directly try and pretend - TestClass.orgObjectDefineProperty(newNavigator, name, - { - configurable: true, - get: function () { - return navigator[name]; - } - }); - } - } - } - - this.setNavigator(newNavigator); - } - - this._beaconHooks.push(cb); - } - - protected setNavigator(newNavigator: any) { - TestClass.orgObjectDefineProperty(window, 'navigator', - { - configurable: true, - get: function () { - return newNavigator; - } - }); - } - - /** - * Sends a JSON response to the provided request. - * @param request The request to respond to. - * @param data Data to respond with. - * @param errorCode Optional error code to send with the request, default is 200 - */ - public sendJsonResponse(request: SinonFakeXMLHttpRequest, data: any, errorCode?: number) { - if (errorCode === undefined) { - errorCode = 200; - } - - request.respond( - errorCode, - { "Content-Type": "application/json" }, - JSON.stringify(data)); - } - - public getPayloadMessages(spy:SinonSpy, includeInit:boolean = false) { - let resultPayload = []; - if (spy.called && spy.args && spy.args.length > 0) { - spy.args.forEach(call => { - call[0].forEach(message => { - // Ignore the internal SendBrowserInfoOnUserInit message (Only occurs when running tests in a browser) - if (includeInit || message.indexOf("AI (Internal): 72 ") === -1) { - resultPayload.push(message); - } - }) - }); - } - - return resultPayload; - } - - public dumpPayloadMessages(spy:SinonSpy) { - let msg = "Sent Messages"; - if (spy.called && spy.args && spy.args.length > 0) { - spy.args[0][0].forEach((value, idx) => { - msg += "\n" + idx + ":" + value; - }); - } - Assert.ok(false, msg); - } -} - -// Configure Sinon -sinon.assert.fail = (msg?) => { - Assert.ok(false, msg); -}; - -sinon.assert.pass = (assertion) => { - Assert.ok(assertion, "sinon assert"); -}; diff --git a/AISKU/Tests/AISKUSize.Tests.ts b/AISKU/Tests/Unit/src/AISKUSize.Tests.ts similarity index 86% rename from AISKU/Tests/AISKUSize.Tests.ts rename to AISKU/Tests/Unit/src/AISKUSize.Tests.ts index 21d8ce587..6901991f5 100644 --- a/AISKU/Tests/AISKUSize.Tests.ts +++ b/AISKU/Tests/Unit/src/AISKUSize.Tests.ts @@ -1,11 +1,10 @@ -import { TestClass } from "./TestFramework/TestClass"; -import { Assert } from "./TestFramework/Assert"; +import { AITestClass, Assert } from "@microsoft/ai-test-framework"; import * as pako from "pako"; -export class AISKUSizeCheck extends TestClass { +export class AISKUSizeCheck extends AITestClass { private readonly MAX_DEFLATE_SIZE = 40; - private readonly rawFilePath = "../../dist/applicationinsights-web.min.js"; - private readonly prodFilePath = "../../browser/ai.2.min.js"; + private readonly rawFilePath = "../dist/applicationinsights-web.min.js"; + private readonly prodFilePath = "../browser/ai.2.min.js"; public testInitialize() { } diff --git a/AISKU/Tests/ApplicationInsightsDeprecatedTests.ts b/AISKU/Tests/Unit/src/ApplicationInsightsDeprecatedTests.ts similarity index 92% rename from AISKU/Tests/ApplicationInsightsDeprecatedTests.ts rename to AISKU/Tests/Unit/src/ApplicationInsightsDeprecatedTests.ts index 222f7b96f..937576f40 100644 --- a/AISKU/Tests/ApplicationInsightsDeprecatedTests.ts +++ b/AISKU/Tests/Unit/src/ApplicationInsightsDeprecatedTests.ts @@ -1,13 +1,11 @@ -import { IAppInsightsDeprecated } from "../src/ApplicationInsightsDeprecated"; -import { ApplicationInsightsContainer } from "../src/ApplicationInsightsContainer"; -import { Snippet } from "../src/Initialization"; +import { IAppInsightsDeprecated } from "../../../src/ApplicationInsightsDeprecated"; +import { ApplicationInsightsContainer } from "../../../src/ApplicationInsightsContainer"; +import { Snippet } from "../../../src/Initialization"; import { Sender } from "@microsoft/applicationinsights-channel-js"; import { SinonSpy } from "sinon"; -import { Assert } from "./TestFramework/Assert"; -import { PollingAssert } from "./TestFramework/PollingAssert"; -import { TestClass } from "./TestFramework/TestClass"; +import { AITestClass, Assert, PollingAssert } from "@microsoft/ai-test-framework"; -export class ApplicationInsightsDeprecatedTests extends TestClass { +export class ApplicationInsightsDeprecatedTests extends AITestClass { private static readonly _instrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11'; private _aiDeprecated: IAppInsightsDeprecated; private _snippet: Snippet; diff --git a/AISKU/Tests/SnippetInitialization.Tests.ts b/AISKU/Tests/Unit/src/SnippetInitialization.Tests.ts similarity index 98% rename from AISKU/Tests/SnippetInitialization.Tests.ts rename to AISKU/Tests/Unit/src/SnippetInitialization.Tests.ts index 0f286b4f1..53dc49fa2 100644 --- a/AISKU/Tests/SnippetInitialization.Tests.ts +++ b/AISKU/Tests/Unit/src/SnippetInitialization.Tests.ts @@ -1,11 +1,9 @@ -import { IAppInsightsDeprecated } from "../src/ApplicationInsightsDeprecated"; -import { ApplicationInsightsContainer } from "../src/ApplicationInsightsContainer"; -import { IApplicationInsights, Snippet } from "../src/Initialization"; +import { IAppInsightsDeprecated } from "../../../src/ApplicationInsightsDeprecated"; +import { ApplicationInsightsContainer } from "../../../src/ApplicationInsightsContainer"; +import { IApplicationInsights, Snippet } from "../../../src/Initialization"; import { Sender } from "@microsoft/applicationinsights-channel-js"; import { SinonSpy } from "sinon"; -import { Assert } from "./TestFramework/Assert"; -import { PollingAssert } from "./TestFramework/PollingAssert"; -import { TestClass } from "./TestFramework/TestClass"; +import { AITestClass, Assert, PollingAssert } from "@microsoft/ai-test-framework"; import { createSnippetV5 } from "./testSnippet"; import { hasOwnProperty, isNotNullOrUndefined, ITelemetryItem, objForEachKey } from "@microsoft/applicationinsights-core-js"; import { ContextTagKeys, DistributedTracingModes, IConfig, IDependencyTelemetry, RequestHeaders, Util } from "@microsoft/applicationinsights-common"; @@ -64,7 +62,7 @@ function getSnippetConfig(sessionPrefix: string) { }; }; -export class SnippetInitializationTests extends TestClass { +export class SnippetInitializationTests extends AITestClass { // Context private tagKeys = new ContextTagKeys(); @@ -183,7 +181,7 @@ export class SnippetInitializationTests extends TestClass { } catch (e) { Assert.ok(false, "Exception:" + e); } - }, "waiting for sender success", 30, 1000)] + }, "waiting for sender success", 30, 1000) as any] }); this.testCase({ @@ -843,8 +841,8 @@ export class SnippetInitializationTests extends TestClass { }); this.testCase({ - name: 'Sampling: sampleRate is generated as a field in the envelope when it is less than 100', - test: () => { + name: 'Sampling: sampleRate is generated as a field in the envelope when it is less than 100', + test:() => { let theSnippet = this._initializeSnippet(snippetCreator(getSnippetConfig(this.sessionPrefix))); theSnippet.trackEvent({ name: 'event' }); Assert.ok(this.envelopeConstructorSpy.called); diff --git a/AISKU/Tests/SnippetLegacyInitialization.Tests.ts b/AISKU/Tests/Unit/src/SnippetLegacyInitialization.Tests.ts similarity index 97% rename from AISKU/Tests/SnippetLegacyInitialization.Tests.ts rename to AISKU/Tests/Unit/src/SnippetLegacyInitialization.Tests.ts index 8f692f2c0..359fa380c 100644 --- a/AISKU/Tests/SnippetLegacyInitialization.Tests.ts +++ b/AISKU/Tests/Unit/src/SnippetLegacyInitialization.Tests.ts @@ -1,12 +1,10 @@ -import { IAppInsightsDeprecated } from "../src/ApplicationInsightsDeprecated"; -import { ApplicationInsightsContainer } from "../src/ApplicationInsightsContainer"; -import { IApplicationInsights, Snippet } from "../src/Initialization"; +import { IAppInsightsDeprecated } from "../../../src/ApplicationInsightsDeprecated"; +import { ApplicationInsightsContainer } from "../../../src/ApplicationInsightsContainer"; +import { IApplicationInsights, Snippet } from "../../../src/Initialization"; import { Sender } from "@microsoft/applicationinsights-channel-js"; import { createLegacySnippet } from "./testLegacySnippet"; import { SinonSpy } from "sinon"; -import { Assert } from "./TestFramework/Assert"; -import { PollingAssert } from "./TestFramework/PollingAssert"; -import { TestClass } from "./TestFramework/TestClass"; +import { AITestClass, Assert, PollingAssert } from "@microsoft/ai-test-framework"; import { hasOwnProperty, isNotNullOrUndefined } from "@microsoft/applicationinsights-core-js"; function getBasicLegacySnippetConfig() { @@ -47,7 +45,7 @@ const _expectedMethodsAfterInitialization = [ "getCookieMgr" ]; -export class SnippetLegacyInitializationTests extends TestClass { +export class SnippetLegacyInitializationTests extends AITestClass { // Sinon private errorSpy: SinonSpy; @@ -153,7 +151,7 @@ export class SnippetLegacyInitializationTests extends TestClass { } catch (e) { Assert.ok(false, "Exception:" + e); } - }, "waiting for sender success", 30, 1000)] + }, "waiting for sender success", 30, 1000) as any] }); this.testCase({ diff --git a/AISKU/Tests/Unit/src/aiskuunittests.ts b/AISKU/Tests/Unit/src/aiskuunittests.ts new file mode 100644 index 000000000..539dd28d4 --- /dev/null +++ b/AISKU/Tests/Unit/src/aiskuunittests.ts @@ -0,0 +1,23 @@ +import { AISKUSizeCheck } from "./AISKUSize.Tests"; +import { ApplicationInsightsTests } from './applicationinsights.e2e.tests'; +import { ApplicationInsightsFetchTests } from './applicationinsights.e2e.fetch.tests'; +import { SanitizerE2ETests } from './sanitizer.e2e.tests'; +import { ValidateE2ETests } from './validate.e2e.tests'; +import { SenderE2ETests } from './sender.e2e.tests'; +import { ApplicationInsightsDeprecatedTests } from './ApplicationInsightsDeprecatedTests'; +import { SnippetLegacyInitializationTests } from './SnippetLegacyInitialization.Tests'; +import { SnippetInitializationTests } from './SnippetInitialization.Tests'; + +export function runTests() { + new AISKUSizeCheck().registerTests(); + new ApplicationInsightsTests().registerTests(); + new ApplicationInsightsFetchTests().registerTests(); + new ApplicationInsightsDeprecatedTests().registerTests(); + new SanitizerE2ETests().registerTests(); + new ValidateE2ETests().registerTests(); + new SenderE2ETests().registerTests(); + new SnippetLegacyInitializationTests(false).registerTests(); + new SnippetInitializationTests(false).registerTests(); + new SnippetLegacyInitializationTests(true).registerTests(); + new SnippetInitializationTests(true).registerTests(); +} \ No newline at end of file diff --git a/AISKU/Tests/applicationinsights.e2e.fetch.tests.ts b/AISKU/Tests/Unit/src/applicationinsights.e2e.fetch.tests.ts similarity index 100% rename from AISKU/Tests/applicationinsights.e2e.fetch.tests.ts rename to AISKU/Tests/Unit/src/applicationinsights.e2e.fetch.tests.ts diff --git a/AISKU/Tests/applicationinsights.e2e.tests.ts b/AISKU/Tests/Unit/src/applicationinsights.e2e.tests.ts similarity index 97% rename from AISKU/Tests/applicationinsights.e2e.tests.ts rename to AISKU/Tests/Unit/src/applicationinsights.e2e.tests.ts index 7e9a581da..1de2fb923 100644 --- a/AISKU/Tests/applicationinsights.e2e.tests.ts +++ b/AISKU/Tests/Unit/src/applicationinsights.e2e.tests.ts @@ -1,21 +1,13 @@ -import { TestClass } from './TestFramework/TestClass'; +import { AITestClass, Assert, PollingAssert, EventValidator, TraceValidator, ExceptionValidator, MetricValidator, PageViewValidator, PageViewPerformanceValidator, RemoteDepdencyValidator } from '@microsoft/ai-test-framework'; import { SinonSpy } from 'sinon'; -import { ApplicationInsights, IApplicationInsights } from '../src/applicationinsights-web' +import { ApplicationInsights, IApplicationInsights } from '../../../src/applicationinsights-web' import { Sender } from '@microsoft/applicationinsights-channel-js'; import { IDependencyTelemetry, ContextTagKeys, Util, Event, Trace, Exception, Metric, PageView, PageViewPerformance, RemoteDependencyData, DistributedTracingModes, RequestHeaders, IAutoExceptionTelemetry } from '@microsoft/applicationinsights-common'; import { AppInsightsCore, ITelemetryItem, getGlobal } from "@microsoft/applicationinsights-core-js"; import { TelemetryContext } from '@microsoft/applicationinsights-properties-js'; -import { EventValidator } from './TelemetryValidation/EventValidator'; -import { TraceValidator } from './TelemetryValidation/TraceValidator'; -import { ExceptionValidator } from './TelemetryValidation/ExceptionValidator'; -import { MetricValidator } from './TelemetryValidation/MetricValidator'; -import { PageViewPerformanceValidator } from './TelemetryValidation/PageViewPerformanceValidator'; -import { PageViewValidator } from './TelemetryValidation/PageViewValidator'; -import { RemoteDepdencyValidator } from './TelemetryValidation/RemoteDepdencyValidator'; -import { Assert } from './TestFramework/Assert'; -import { PollingAssert } from './TestFramework/PollingAssert'; - -export class ApplicationInsightsTests extends TestClass { + + +export class ApplicationInsightsTests extends AITestClass { private static readonly _instrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11'; private static readonly _connectionString = `InstrumentationKey=${ApplicationInsightsTests._instrumentationKey}`; private static readonly _expectedTrackMethods = [ @@ -108,7 +100,7 @@ export class ApplicationInsightsTests extends TestClass { this._ai["dependencies"].teardown(); } - console.log("* testCleanup(" + (TestClass.currentTestInfo ? TestClass.currentTestInfo.name : "") + ")"); + console.log("* testCleanup(" + (AITestClass.currentTestInfo ? AITestClass.currentTestInfo.name : "") + ")"); } public registerTests() { diff --git a/AISKU/Tests/sanitizer.e2e.tests.ts b/AISKU/Tests/Unit/src/sanitizer.e2e.tests.ts similarity index 96% rename from AISKU/Tests/sanitizer.e2e.tests.ts rename to AISKU/Tests/Unit/src/sanitizer.e2e.tests.ts index 38dc16ee6..f923186ff 100644 --- a/AISKU/Tests/sanitizer.e2e.tests.ts +++ b/AISKU/Tests/Unit/src/sanitizer.e2e.tests.ts @@ -1,11 +1,9 @@ -import { ApplicationInsights, IApplicationInsights, Util, LoggingSeverity, _InternalMessageId } from '../src/applicationinsights-web' +import { ApplicationInsights, IApplicationInsights, Util, LoggingSeverity, _InternalMessageId } from '../../../src/applicationinsights-web' import { Sender } from '@microsoft/applicationinsights-channel-js'; -import { TestClass } from './TestFramework/TestClass'; +import { AITestClass, Assert, PollingAssert } from '@microsoft/ai-test-framework'; import { SinonSpy } from 'sinon'; -import { Assert } from './TestFramework/Assert'; -import { PollingAssert } from './TestFramework/PollingAssert'; -export class SanitizerE2ETests extends TestClass { +export class SanitizerE2ETests extends AITestClass { private readonly _instrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11'; private _ai: IApplicationInsights; diff --git a/AISKU/Tests/sender.e2e.tests.ts b/AISKU/Tests/Unit/src/sender.e2e.tests.ts similarity index 95% rename from AISKU/Tests/sender.e2e.tests.ts rename to AISKU/Tests/Unit/src/sender.e2e.tests.ts index 6ff2eca02..d931d7914 100644 --- a/AISKU/Tests/sender.e2e.tests.ts +++ b/AISKU/Tests/Unit/src/sender.e2e.tests.ts @@ -1,13 +1,11 @@ -import { ApplicationInsights, IApplicationInsights } from '../src/applicationinsights-web' +import { ApplicationInsights, IApplicationInsights } from '../../../src/applicationinsights-web' import { Sender } from '@microsoft/applicationinsights-channel-js'; import { Util } from '@microsoft/applicationinsights-common'; import { getJSON } from '@microsoft/applicationinsights-core-js'; import { SinonSpy } from 'sinon'; -import { Assert } from './TestFramework/Assert'; -import { PollingAssert } from './TestFramework/PollingAssert'; -import { TestClass } from './TestFramework/TestClass'; +import { Assert, AITestClass, PollingAssert} from "@microsoft/ai-test-framework" -export class SenderE2ETests extends TestClass { +export class SenderE2ETests extends AITestClass { private readonly _instrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11'; private readonly _bufferName = 'AI_buffer'; private readonly _sentBufferName = 'AI_sentBuffer'; @@ -196,6 +194,7 @@ export class SenderE2ETests extends TestClass { } } } catch (e) { + console.error("_getBuffer" + e); } return []; diff --git a/AISKU/Tests/testLegacySnippet.ts b/AISKU/Tests/Unit/src/testLegacySnippet.ts similarity index 100% rename from AISKU/Tests/testLegacySnippet.ts rename to AISKU/Tests/Unit/src/testLegacySnippet.ts diff --git a/AISKU/Tests/testSnippet.ts b/AISKU/Tests/Unit/src/testSnippet.ts similarity index 100% rename from AISKU/Tests/testSnippet.ts rename to AISKU/Tests/Unit/src/testSnippet.ts diff --git a/AISKU/Tests/validate.e2e.tests.ts b/AISKU/Tests/Unit/src/validate.e2e.tests.ts similarity index 95% rename from AISKU/Tests/validate.e2e.tests.ts rename to AISKU/Tests/Unit/src/validate.e2e.tests.ts index c13e8a308..8a707d82f 100644 --- a/AISKU/Tests/validate.e2e.tests.ts +++ b/AISKU/Tests/Unit/src/validate.e2e.tests.ts @@ -1,11 +1,9 @@ -import { ApplicationInsights, IApplicationInsights } from '../src/applicationinsights-web' +import { ApplicationInsights, IApplicationInsights } from '../../../src/applicationinsights-web' import { Sender } from '@microsoft/applicationinsights-channel-js'; import { SinonSpy } from 'sinon'; -import { Assert } from './TestFramework/Assert'; -import { PollingAssert } from './TestFramework/PollingAssert'; -import { TestClass } from './TestFramework/TestClass'; +import { AITestClass, Assert, PollingAssert } from '@microsoft/ai-test-framework'; -export class ValidateE2ETests extends TestClass { +export class ValidateE2ETests extends AITestClass { private readonly _instrumentationKey = 'b7170927-2d1c-44f1-acec-59f4e1751c11'; private _ai: IApplicationInsights; diff --git a/AISKU/Tests/Selenium/Tests.html b/AISKU/Tests/UnitTests.html similarity index 76% rename from AISKU/Tests/Selenium/Tests.html rename to AISKU/Tests/UnitTests.html index 11c33e360..cdd33ae8e 100644 --- a/AISKU/Tests/Selenium/Tests.html +++ b/AISKU/Tests/UnitTests.html @@ -4,25 +4,20 @@ - + Tests for Application Insights JavaScript AISKU - Tests for Application Insights JavaScript API - + + - - -
-
+ + + +
+
+
- + + \ No newline at end of file diff --git a/AISKU/Tests/tsconfig.json b/AISKU/Tests/tsconfig.json index aab705d12..c73eae51b 100644 --- a/AISKU/Tests/tsconfig.json +++ b/AISKU/Tests/tsconfig.json @@ -7,14 +7,7 @@ "moduleResolution": "Node", "target": "es5", "alwaysStrict": true, - "declaration": true, - "out": "Tests/Selenium/appinsights-sdk.tests.js" + "declaration": true }, - "include": [ - "Tests/Selenium/*.ts", - "Tests/*.ts" - ], - "exclude": [ - "node_modules/" - ] + "files": [] } diff --git a/AISKU/Tests/validate.e2e.deprecated.tests.ts b/AISKU/Tests/validate.e2e.deprecated.tests.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/AISKU/package.json b/AISKU/package.json index f8468d4d6..d0200cfe1 100644 --- a/AISKU/package.json +++ b/AISKU/package.json @@ -19,7 +19,8 @@ "build:browser": "rollup -c rollup.config.js", "build:snippet": "grunt snippetvnext", "rebuild": "npm run build", - "test": "grunt aiskutests", + "test": "grunt aiskuunittests", + "perftest": "grunt aiskuperf", "lint": "tslint -p tsconfig.json", "dtsgen": "api-extractor run --local && node ../scripts/dtsgen.js 'Microsoft.ApplicationInsights'", "sri": "node ../tools/subResourceIntegrity/generateIntegrityFile.js", @@ -34,6 +35,7 @@ "nightwatch:done": "curl http://localhost:8000/_done" }, "devDependencies": { + "@microsoft/ai-test-framework": "0.0.1", "@microsoft/applicationinsights-rollup-plugin-uglify3-js": "1.0.0", "@microsoft/applicationinsights-rollup-es3": "1.1.3", "sinon": "^7.3.1", diff --git a/common/config/rush/npm-shrinkwrap.json b/common/config/rush/npm-shrinkwrap.json index 0b2679a89..c7a83d613 100644 --- a/common/config/rush/npm-shrinkwrap.json +++ b/common/config/rush/npm-shrinkwrap.json @@ -3846,7 +3846,7 @@ "node_modules/@rush-temp/applicationinsights-web": { "version": "0.0.0", "resolved": "file:projects/applicationinsights-web.tgz", - "integrity": "sha512-JmWVytZEIOwCCNFrBzMm38/yXmgeFltUl6WB0gPE9SbuGPix2GFq7jkKmgGbVP4rYH2PTpkvu0CF7PDU8oFOlQ==", + "integrity": "sha512-X14k+mPyz5fM3Z/cDzYqkhJHCpX0b1gcwMiEX4gYRxgbTcr3Ym+F52dIJ9Dspou/Lug0S+115XI0Qk9mtMxuUA==", "dependencies": { "@microsoft/api-extractor": "^7.18.1", "@microsoft/dynamicproto-js": "^1.1.4", @@ -6653,9 +6653,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "node_modules/electron-to-chromium": { - "version": "1.3.888", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.888.tgz", - "integrity": "sha512-5iD1zgyPpFER4kJ716VsA4MxQ6x405dxdFNCEK2mITL075VHO5ResjY0xzQUZguCww/KlBxCA6JmBA9sDt1PRw==" + "version": "1.3.889", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.889.tgz", + "integrity": "sha512-suEUoPTD1mExjL9TdmH7cvEiWJVM2oEiAi+Y1p0QKxI2HcRlT44qDTP2c1aZmVwRemIPYOpxmV7CxQCOWcm4XQ==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -20950,7 +20950,7 @@ }, "@rush-temp/applicationinsights-web": { "version": "file:projects\\applicationinsights-web.tgz", - "integrity": "sha512-JmWVytZEIOwCCNFrBzMm38/yXmgeFltUl6WB0gPE9SbuGPix2GFq7jkKmgGbVP4rYH2PTpkvu0CF7PDU8oFOlQ==", + "integrity": "sha512-X14k+mPyz5fM3Z/cDzYqkhJHCpX0b1gcwMiEX4gYRxgbTcr3Ym+F52dIJ9Dspou/Lug0S+115XI0Qk9mtMxuUA==", "requires": { "@microsoft/api-extractor": "^7.18.1", "@microsoft/dynamicproto-js": "^1.1.4", @@ -23163,9 +23163,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.888", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.888.tgz", - "integrity": "sha512-5iD1zgyPpFER4kJ716VsA4MxQ6x405dxdFNCEK2mITL075VHO5ResjY0xzQUZguCww/KlBxCA6JmBA9sDt1PRw==" + "version": "1.3.889", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.889.tgz", + "integrity": "sha512-suEUoPTD1mExjL9TdmH7cvEiWJVM2oEiAi+Y1p0QKxI2HcRlT44qDTP2c1aZmVwRemIPYOpxmV7CxQCOWcm4XQ==" }, "emoji-regex": { "version": "8.0.0", diff --git a/gruntfile.js b/gruntfile.js index 4e4d449f7..5c8bbfc24 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -91,6 +91,11 @@ module.exports = function (grunt) { return buildCmds; } + // const perfTestVersions = ["2.0.0","2.0.1","2.1.0","2.2.0","2.2.1","2.2.2","2.3.0","2.3.1", + // "2.4.1","2.4.3","2.4.4","2.5.2","2.5.3","2.5.4","2.5.5","2.5.6","2.5.7","2.5.8","2.5.9","2.5.10","2.5.11", + // "2.6.0","2.6.1","2.6.2","2.6.3","2.6.4","2.6.5","2.7.0"]; + const perfTestVersions=["2.7.1"] + try { var theBuildConfig = deepMerge(buildConfig({ // Shared @@ -199,13 +204,19 @@ module.exports = function (grunt) { ], out: 'extensions/applicationinsights-analytics-js/Tests/Unit/dist/appinsights-analytics.tests.js' }, - aiskutests: { + aiskuunittests: { tsconfig: './AISKU/Tests/tsconfig.json', src: [ - 'AISKU/Tests/Selenium/*.ts', - 'AISKU/Tests/*.ts' + './AISKU/Tests/Unit/src/**/*.ts', ], - out: 'AISKU/Tests/Selenium/appinsights-sdk.tests.js' + out: 'AISKU/Tests/Unit/dist/aiskuunittests.tests.js' + }, + aiskuperf: { + tsconfig: './AISKU/Tests/tsconfig.json', + src: [ + './AISKU/Tests/Perf/src/**/*.ts', + ], + out: 'AISKU/Tests/Perf/dist/aiskuperftests.tests.js' }, clickanalyticstests: { tsconfig: './extensions/applicationinsights-clickanalytics-js/Tests/tsconfig.json', @@ -419,7 +430,7 @@ module.exports = function (grunt) { aisku: { options: { urls: [ - 'http://localhost:9001/AISKU/Tests/Selenium/Tests.html' + 'http://localhost:9001/AISKU/Tests/UnitTests.html' ], timeout: 5 * 60 * 1000, // 5 min console: true, @@ -427,6 +438,19 @@ module.exports = function (grunt) { '--web-security': 'false' } }, + aiskuperf: { + options: { + urls: perfTestVersions.map((version) => { + return `http://localhost:9001/AISKU/Tests/PerfTests.html?version=${version}` + + }), + timeout: 5 * 60 * 1000, // 5 min + console: true, + summaryOnly: false, + puppeteer: { headless: true, args:['--enable-precise-memory-info','--expose-internals-for-testing'] }, + '--web-security': 'false' + } + }, aichannel: { options: { urls: [ @@ -499,7 +523,8 @@ module.exports = function (grunt) { grunt.registerTask("aisku", tsBuildActions("aisku")); grunt.registerTask("aiskulite", tsBuildActions("aiskulite")); grunt.registerTask("snippetvnext", ["uglify:snippetvNext"]); - grunt.registerTask("aiskutests", ["connect", "ts:aiskutests", "qunit:aisku"]); + grunt.registerTask("aiskuunittests", ["connect", "ts:aiskuunittests", "qunit:aisku"]); + grunt.registerTask("aiskuperf", ["connect", "ts:aiskuperf", "qunit:aiskuperf"]); grunt.registerTask("test", ["connect", "ts:default", "ts:test", "ts:testSchema", "ts:testE2E", "qunit:all"]); grunt.registerTask("test1ds", ["coretest", "common", "propertiestests", "depstest", "aitests", "aiskutests", "reactnativetests", "reacttests"]); grunt.registerTask("coreunittest", ["connect", "ts:coreunittest", "qunit:core"]);