From c6218b02bdd13d5b3b4e5756cb45ce06e6d7e22b Mon Sep 17 00:00:00 2001 From: ijemmy Date: Mon, 21 Mar 2022 17:33:34 +0100 Subject: [PATCH 01/12] chore(metrics): refactor metrics e2e to make stack and resource name unique --- packages/commons/src/index.ts | 1 + .../commons/src/utils/e2e/InvocationLogs.ts | 90 ++++++ packages/commons/src/utils/e2e/e2eUtils.ts | 123 +++++++++ packages/commons/src/utils/e2e/index.ts | 2 + .../tests/e2e/sampleRate.decorator.test.ts | 4 +- .../tests/e2e/standardFunctions.test.ts | 256 +++++++++--------- .../tests/helpers/metricsDeploymentUtils.ts | 0 7 files changed, 345 insertions(+), 131 deletions(-) create mode 100644 packages/commons/src/utils/e2e/InvocationLogs.ts create mode 100644 packages/commons/src/utils/e2e/e2eUtils.ts create mode 100644 packages/commons/src/utils/e2e/index.ts create mode 100644 packages/metrics/tests/helpers/metricsDeploymentUtils.ts diff --git a/packages/commons/src/index.ts b/packages/commons/src/index.ts index 1a056dbf93..9b6f264d81 100644 --- a/packages/commons/src/index.ts +++ b/packages/commons/src/index.ts @@ -1,4 +1,5 @@ export * from './utils/lambda'; +export * from './utils/e2e'; export * from './Utility'; export * as ContextExamples from './tests/resources/contexts'; export * as Events from './tests/resources/events'; \ No newline at end of file diff --git a/packages/commons/src/utils/e2e/InvocationLogs.ts b/packages/commons/src/utils/e2e/InvocationLogs.ts new file mode 100644 index 0000000000..fab917ca8d --- /dev/null +++ b/packages/commons/src/utils/e2e/InvocationLogs.ts @@ -0,0 +1,90 @@ +/** + * Log level. used for filtering the log + */ +export enum LEVEL { + DEBUG = 'DEBUG', + INFO = 'INFO', + WARN = 'WARN', + ERROR = 'ERROR', +} + +export type FunctionLog = { + timestamp: string + invocationId: string + logLevel: LEVEL + // eslint-disable-next-line @typescript-eslint/no-explicit-any + logObject: {[key: string]: any} +}; +export class InvocationLogs { + public static LEVEL = LEVEL; + + /** + * Array of logs from invocation. + * + * The first element is START, and the last two elements are END, and REPORT. + * In each log, each content is separated by '\t' + * [ + * 'START RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678 Version: $LATEST', + * '2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tINFO\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"INFO","message":"This is an INFO log with some context and persistent key","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works"}', + * '2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tINFO\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"INFO","message":"This is an INFO log with some context","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","additionalKey":"additionalValue"}', + * '2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tERROR\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"ERROR","message":"There was an error","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","error":{"name":"Error","location":"/var/task/index.js:2778","message":"you cannot prevent this","stack":"Error: you cannot prevent this\\n at testFunction (/var/task/index.js:2778:11)\\n at runRequest (/var/task/index.js:2314:36)"}}', + * 'END RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678', + * 'REPORT RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678\tDuration: 2.16 ms\tBilled Duration: 3 ms\tMemory Size: 128 MB\tMax Memory Used: 57 MB\t', + * ] + * See https://docs.aws.amazon.com/lambda/latest/dg/nodejs-logging.html for details + */ + private logs: string[]; + + public constructor(logResult: string) { + const rawLog = Buffer.from(logResult, 'base64').toString('utf-8').trim(); + this.logs = rawLog.split('\n'); + } + + /** + * Find all functional logs whether it contains a given key + * @param key + * @param log level to filter + * @returns + */ + public doesAnyFunctionLogsContains(key: string, levelToFilter?: LEVEL): boolean { + const filteredLogs = this.getFunctionLogs(levelToFilter) + .filter(log => log.includes(key)); + + return filteredLogs.length > 0; + } + + /** + * Return only logs from function, excude START, END, and REPORT generated by Lambda service + * @param log level to filter + * @returns Array of function logs + */ + public getFunctionLogs(levelToFilter?: LEVEL): string[] { + let filteredLogs = this.logs.slice(1, -2); + + if (levelToFilter) { + filteredLogs = filteredLogs.filter(log => log.includes(levelToFilter.toString())); + } + + return filteredLogs; + } + + /** + * Each of log message contains 4 elements, separted by tabs + * 1. Timestamp (e.g. 2022-01-27T16:04:39.323Z ) + * 2. Invocation id (e.g. tafb6de1a-48f8-4fbb-ad72-23a4f0c2924c) + * 3. Log level (e.g. INFO) + * 4. Log object (e.g. {\"cold_start\":true, ..}) + * @param message + */ + public static parseFunctionLog(log: string): FunctionLog { + const elements = log.split('\t'); + + return { + timestamp: elements[0], + invocationId: elements[1], + logLevel: elements[2], + logObject: JSON.parse(elements[3]), + }; + } + +} \ No newline at end of file diff --git a/packages/commons/src/utils/e2e/e2eUtils.ts b/packages/commons/src/utils/e2e/e2eUtils.ts new file mode 100644 index 0000000000..f455d9b07e --- /dev/null +++ b/packages/commons/src/utils/e2e/e2eUtils.ts @@ -0,0 +1,123 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +import { CloudFormationStackArtifact } from '@aws-cdk/cx-api'; +import { SdkProvider } from 'aws-cdk/lib/api/aws-auth'; +import { CloudFormationDeployments } from 'aws-cdk/lib/api/cloudformation-deployments'; +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda-nodejs'; +import { Runtime, Tracing } from '@aws-cdk/aws-lambda'; +import * as AWS from 'aws-sdk'; + +import { InvocationLogs } from './InvocationLogs'; + +const lambdaClient = new AWS.Lambda(); + +const testRuntimeKeys = [ 'nodejs12x', 'nodejs14x' ]; +export type TestRuntimesKey = typeof testRuntimeKeys[number]; +const TEST_RUNTIMES: Record = { + nodejs12x: Runtime.NODEJS_12_X, + nodejs14x: Runtime.NODEJS_14_X, +}; + +export type StackWithLambdaFunctionOptions = { + app: App + stackName: string + functionName: string + functionEntry: string + tracing?: Tracing + environment: {[key: string]: string} + logGroupOutputKey?: string + runtime: string +}; + +export const isValidRuntimeKey = (runtime: string): runtime is TestRuntimesKey => testRuntimeKeys.includes(runtime); + +export const createStackWithLambdaFunction = (params: StackWithLambdaFunctionOptions): Stack => { + + const stack = new Stack(params.app, params.stackName); + const testFunction = new lambda.NodejsFunction(stack, `testFunction`, { + functionName: params.functionName, + entry: params.functionEntry, + tracing: params.tracing, + environment: params.environment, + runtime: TEST_RUNTIMES[params.runtime as TestRuntimesKey], + }); + + if (params.logGroupOutputKey) { + new CfnOutput(stack, params.logGroupOutputKey, { + value: testFunction.logGroup.logGroupName, + }); + } + + return stack; +}; + +export const generateUniqueName = (name_prefix: string, uuid: string, runtime: string, testName: string): string => + `${name_prefix}-${runtime}-${testName}-${uuid}`.substring(0, 64); + +export const deployStack = async (stackArtifact: CloudFormationStackArtifact ): Promise<{[name:string]: string}> => { + const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ + profile: process.env.AWS_PROFILE, + }); + const cloudFormation = new CloudFormationDeployments({ sdkProvider }); + + // WHEN lambda function is deployed + const result = await cloudFormation.deployStack({ + stack: stackArtifact, + quiet: true, + }); + + return result.outputs; +}; + +export const invokeFunction = async (functionName: string, times: number = 1, invocationMode: 'PARALLEL' | 'SEQUENTIAL' = 'PARALLEL'): Promise => { + const invocationLogs: InvocationLogs[] = []; + + const promiseFactory = () : Promise => { + const invokePromise = lambdaClient + .invoke({ + FunctionName: functionName, + LogType: 'Tail', // Wait until execution completes and return all logs + }) + .promise() + .then((response) => { + if (response?.LogResult) { + invocationLogs.push(new InvocationLogs(response?.LogResult)); + } else { + throw new Error('No LogResult field returned in the response of Lambda invocation. This should not happen.'); + } + }); + + return invokePromise; + }; + + const promiseFactories = Array.from({ length: times }, () => promiseFactory ); + const invocation = invocationMode == 'PARALLEL' + ? Promise.all(promiseFactories.map(factory => factory())) + : chainPromises(promiseFactories); + await invocation; + + return invocationLogs; +}; + +export const destroyStack = async (app: App, stack: Stack): Promise => { + const stackArtifact = app.synth().getStackByName(stack.stackName); + + const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ + profile: process.env.AWS_PROFILE, + }); + const cloudFormation = new CloudFormationDeployments({ sdkProvider }); + + await cloudFormation.destroyStack({ + stack: stackArtifact, + quiet: true, + }); +}; + +const chainPromises = async (promiseFactories: (() => Promise)[]) : Promise => { + let chain = Promise.resolve(); + promiseFactories.forEach(factory => chain = chain.then(factory)); + + return chain; +}; diff --git a/packages/commons/src/utils/e2e/index.ts b/packages/commons/src/utils/e2e/index.ts new file mode 100644 index 0000000000..fb411a13a3 --- /dev/null +++ b/packages/commons/src/utils/e2e/index.ts @@ -0,0 +1,2 @@ +export * from './e2eUtils'; +export * from './InvocationLogs'; \ No newline at end of file diff --git a/packages/logger/tests/e2e/sampleRate.decorator.test.ts b/packages/logger/tests/e2e/sampleRate.decorator.test.ts index 1d2eac93c4..8995dc70b7 100644 --- a/packages/logger/tests/e2e/sampleRate.decorator.test.ts +++ b/packages/logger/tests/e2e/sampleRate.decorator.test.ts @@ -34,9 +34,11 @@ const lambdaFunctionCodeFile = 'sampleRate.decorator.test.FunctionCode.ts'; const LOG_MSG = `Log message ${uuid}`; const SAMPLE_RATE = '0.5'; const LOG_LEVEL = LEVEL.ERROR.toString(); + const integTestApp = new App(); -let logGroupName: string; // We do not know it until deployment let stack: Stack; +let logGroupName: string; // We do not know the exact name until deployment + describe(`logger E2E tests sample rate and injectLambdaContext() for runtime: ${runtime}`, () => { let invocationLogs: InvocationLogs[]; diff --git a/packages/metrics/tests/e2e/standardFunctions.test.ts b/packages/metrics/tests/e2e/standardFunctions.test.ts index 975a5a3652..d6d4c7f12a 100644 --- a/packages/metrics/tests/e2e/standardFunctions.test.ts +++ b/packages/metrics/tests/e2e/standardFunctions.test.ts @@ -8,27 +8,38 @@ */ import { randomUUID } from 'crypto'; -import * as lambda from '@aws-cdk/aws-lambda-nodejs'; import { Tracing } from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; -import { SdkProvider } from 'aws-cdk/lib/api/aws-auth'; -import { CloudFormationDeployments } from 'aws-cdk/lib/api/cloudformation-deployments'; import * as AWS from 'aws-sdk'; import { MetricUnits } from '../../src'; import { getMetrics } from '../helpers/metricsUtils'; +import { generateUniqueName, isValidRuntimeKey, createStackWithLambdaFunction, deployStack, invokeFunction, destroyStack } from '@aws-lambda-powertools/commons'; +import path from 'path'; -const ONE_MINUTE = 1000 * 60; +const runtime: string = process.env.RUNTIME || 'nodejs14x'; + +if (!isValidRuntimeKey(runtime)) { + throw new Error(`Invalid runtime key value: ${runtime}`); +} + +const RESOURCE_NAME_PREFIX = 'Metrics-E2E'; +const ONE_MINUTE = 60 * 10_00; +const TEST_CASE_TIMEOUT = 30_000; // 30 seconds +const SETUP_TIMEOUT = 300_000; // 300 seconds +const TEARDOWN_TIMEOUT = 200_000; const cloudwatchClient = new AWS.CloudWatch(); -const lambdaClient = new AWS.Lambda(); -const integTestApp = new App(); -const stack = new Stack(integTestApp, 'MetricsE2EStandardFunctionsStack'); +const uuid = randomUUID(); +const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'Decorator'); +const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'Decorator'); +const lambdaFunctionCodeFile = 'standardFunctions.test.MyFunction.ts'; -// GIVEN const invocationCount = 2; const startTime = new Date(); -const expectedNamespace = randomUUID(); // to easily find metrics back at assert phase + +// Parameters to be used by Metrics in the Lambda function +const expectedNamespace = uuid; // to easily find metrics back at assert phase const expectedServiceName = 'MyFunctionWithStandardHandler'; const expectedMetricName = 'MyMetric'; const expectedMetricUnit = MetricUnits.Count; @@ -39,137 +50,122 @@ const expectedSingleMetricDimension = { MySingleMetricDim: 'MySingleValue' }; const expectedSingleMetricName = 'MySingleMetric'; const expectedSingleMetricUnit = MetricUnits.Percent; const expectedSingleMetricValue = '2'; -const functionName = 'MyFunctionWithStandardHandler'; -new lambda.NodejsFunction(stack, 'MyFunction', { - functionName: functionName, - tracing: Tracing.ACTIVE, - environment: { - EXPECTED_NAMESPACE: expectedNamespace, - EXPECTED_SERVICE_NAME: expectedServiceName, - EXPECTED_METRIC_NAME: expectedMetricName, - EXPECTED_METRIC_UNIT: expectedMetricUnit, - EXPECTED_METRIC_VALUE: expectedMetricValue, - EXPECTED_DEFAULT_DIMENSIONS: JSON.stringify(expectedDefaultDimensions), - EXPECTED_EXTRA_DIMENSION: JSON.stringify(expectedExtraDimension), - EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify(expectedSingleMetricDimension), - EXPECTED_SINGLE_METRIC_NAME: expectedSingleMetricName, - EXPECTED_SINGLE_METRIC_UNIT: expectedSingleMetricUnit, - EXPECTED_SINGLE_METRIC_VALUE: expectedSingleMetricValue, - }, -}); -const stackArtifact = integTestApp.synth().getStackByName(stack.stackName); +const integTestApp = new App(); +let stack: Stack; -describe('happy cases', () => { +describe(`metrics E2E tests (manual) for runtime: ${runtime}`, () => { beforeAll(async () => { - const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ - profile: process.env.AWS_PROFILE, - }); - const cloudFormation = new CloudFormationDeployments({ sdkProvider }); - - // WHEN - // lambda function is deployed - await cloudFormation.deployStack({ - stack: stackArtifact, - quiet: true, + // GIVEN a stack + stack = createStackWithLambdaFunction({ + app: integTestApp, + stackName: stackName, + functionName: functionName, + functionEntry: path.join(__dirname, lambdaFunctionCodeFile), + tracing: Tracing.ACTIVE, + environment: { + POWERTOOLS_SERVICE_NAME: 'metrics-e2e-testing', + UUID: uuid, + + // Parameter(s) to be used by Metrics in the Lambda function + EXPECTED_NAMESPACE: expectedNamespace, + EXPECTED_SERVICE_NAME: expectedServiceName, + EXPECTED_METRIC_NAME: expectedMetricName, + EXPECTED_METRIC_UNIT: expectedMetricUnit, + EXPECTED_METRIC_VALUE: expectedMetricValue, + EXPECTED_DEFAULT_DIMENSIONS: JSON.stringify(expectedDefaultDimensions), + EXPECTED_EXTRA_DIMENSION: JSON.stringify(expectedExtraDimension), + EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify(expectedSingleMetricDimension), + EXPECTED_SINGLE_METRIC_NAME: expectedSingleMetricName, + EXPECTED_SINGLE_METRIC_UNIT: expectedSingleMetricUnit, + EXPECTED_SINGLE_METRIC_VALUE: expectedSingleMetricValue, + }, + runtime: runtime, }); + const stackArtifact = integTestApp.synth().getStackByName(stack.stackName); + await deployStack(stackArtifact); // and invoked - for (let i = 0; i < invocationCount; i++) { - await lambdaClient - .invoke({ - FunctionName: functionName, - }) + await invokeFunction(functionName, invocationCount, 'SEQUENTIAL'); + + }, SETUP_TIMEOUT); + + describe('ColdStart metrics', () => { + it('should capture ColdStart Metric', async () => { + // Check coldstart metric dimensions + const coldStartMetrics = await getMetrics(cloudwatchClient, expectedNamespace, 'ColdStart', 1); + + expect(coldStartMetrics.Metrics?.length).toBe(1); + const coldStartMetric = coldStartMetrics.Metrics?.[0]; + expect(coldStartMetric?.Dimensions).toStrictEqual([{ Name: 'service', Value: expectedServiceName }]); + + // Check coldstart metric value + const adjustedStartTime = new Date(startTime.getTime() - 60 * 1000); + const endTime = new Date(new Date().getTime() + 60 * 1000); + console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ColdStart --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify([{ Name: 'service', Value: expectedServiceName }])}'`); + const coldStartMetricStat = await cloudwatchClient + .getMetricStatistics( + { + Namespace: expectedNamespace, + StartTime: adjustedStartTime, + Dimensions: [{ Name: 'service', Value: expectedServiceName }], + EndTime: endTime, + Period: 60, + MetricName: 'ColdStart', + Statistics: ['Sum'], + }, + undefined + ) + .promise(); + + // Despite lambda has been called twice, coldstart metric sum should only be 1 + const singleDataPoint = coldStartMetricStat.Datapoints ? coldStartMetricStat.Datapoints[0] : {}; + expect(singleDataPoint?.Sum).toBe(1); + }, TEST_CASE_TIMEOUT); + }); + + describe('Default and extra dimensions', () => { + it('should produce a Metric with the default and extra one dimensions', async () => { + // Check metric dimensions + const metrics = await getMetrics(cloudwatchClient, expectedNamespace, expectedMetricName, 1); + + expect(metrics.Metrics?.length).toBe(1); + const metric = metrics.Metrics?.[0]; + const expectedDimensions = [ + { Name: 'service', Value: expectedServiceName }, + { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, + { Name: Object.keys(expectedExtraDimension)[0], Value: expectedExtraDimension.MyExtraDimension }, + ]; + expect(metric?.Dimensions).toStrictEqual(expectedDimensions); + + // Check coldstart metric value + const adjustedStartTime = new Date(startTime.getTime() - 3 * ONE_MINUTE); + const endTime = new Date(new Date().getTime() + ONE_MINUTE); + console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ${expectedMetricName} --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify(expectedDimensions)}'`); + const metricStat = await cloudwatchClient + .getMetricStatistics( + { + Namespace: expectedNamespace, + StartTime: adjustedStartTime, + Dimensions: expectedDimensions, + EndTime: endTime, + Period: 60, + MetricName: expectedMetricName, + Statistics: ['Sum'], + }, + undefined + ) .promise(); - } - // THEN - // sleep to allow metrics to be collected - await new Promise((resolve) => setTimeout(resolve, 15000)); - }, ONE_MINUTE * 3); - - it('capture ColdStart Metric', async () => { - // Check coldstart metric dimensions - const coldStartMetrics = await getMetrics(cloudwatchClient, expectedNamespace, 'ColdStart', 1); - - expect(coldStartMetrics.Metrics?.length).toBe(1); - const coldStartMetric = coldStartMetrics.Metrics?.[0]; - expect(coldStartMetric?.Dimensions).toStrictEqual([{ Name: 'service', Value: expectedServiceName }]); - - // Check coldstart metric value - const adjustedStartTime = new Date(startTime.getTime() - 60 * 1000); - const endTime = new Date(new Date().getTime() + 60 * 1000); - console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ColdStart --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify([{ Name: 'service', Value: expectedServiceName }])}'`); - const coldStartMetricStat = await cloudwatchClient - .getMetricStatistics( - { - Namespace: expectedNamespace, - StartTime: adjustedStartTime, - Dimensions: [{ Name: 'service', Value: expectedServiceName }], - EndTime: endTime, - Period: 60, - MetricName: 'ColdStart', - Statistics: ['Sum'], - }, - undefined - ) - .promise(); - - // Despite lambda has been called twice, coldstart metric sum should only be 1 - const singleDataPoint = coldStartMetricStat.Datapoints ? coldStartMetricStat.Datapoints[0] : {}; - expect(singleDataPoint?.Sum).toBe(1); - }, ONE_MINUTE * 3); - - it('produce added Metric with the default and extra one dimensions', async () => { - // Check metric dimensions - const metrics = await getMetrics(cloudwatchClient, expectedNamespace, expectedMetricName, 1); - - expect(metrics.Metrics?.length).toBe(1); - const metric = metrics.Metrics?.[0]; - const expectedDimensions = [ - { Name: 'service', Value: expectedServiceName }, - { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, - { Name: Object.keys(expectedExtraDimension)[0], Value: expectedExtraDimension.MyExtraDimension }, - ]; - expect(metric?.Dimensions).toStrictEqual(expectedDimensions); - - // Check coldstart metric value - const adjustedStartTime = new Date(startTime.getTime() - 3 * ONE_MINUTE); - const endTime = new Date(new Date().getTime() + ONE_MINUTE); - console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ${expectedMetricName} --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify(expectedDimensions)}'`); - const metricStat = await cloudwatchClient - .getMetricStatistics( - { - Namespace: expectedNamespace, - StartTime: adjustedStartTime, - Dimensions: expectedDimensions, - EndTime: endTime, - Period: 60, - MetricName: expectedMetricName, - Statistics: ['Sum'], - }, - undefined - ) - .promise(); - - // Since lambda has been called twice in this test and potentially more in others, metric sum should be at least of expectedMetricValue * invocationCount - const singleDataPoint = metricStat.Datapoints ? metricStat.Datapoints[0] : {}; - expect(singleDataPoint.Sum).toBeGreaterThanOrEqual(parseInt(expectedMetricValue) * invocationCount); - }, ONE_MINUTE * 3); + // Since lambda has been called twice in this test and potentially more in others, metric sum should be at least of expectedMetricValue * invocationCount + const singleDataPoint = metricStat.Datapoints ? metricStat.Datapoints[0] : {}; + expect(singleDataPoint.Sum).toBeGreaterThanOrEqual(parseInt(expectedMetricValue) * invocationCount); + }, TEST_CASE_TIMEOUT); + }); afterAll(async () => { if (!process.env.DISABLE_TEARDOWN) { - const stackArtifact = integTestApp.synth().getStackByName(stack.stackName); - - const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ - profile: process.env.AWS_PROFILE, - }); - const cloudFormation = new CloudFormationDeployments({ sdkProvider }); - - await cloudFormation.destroyStack({ - stack: stackArtifact, - quiet: true, - }); + await destroyStack(integTestApp, stack); } - }, ONE_MINUTE * 3); + }, TEARDOWN_TIMEOUT); }); diff --git a/packages/metrics/tests/helpers/metricsDeploymentUtils.ts b/packages/metrics/tests/helpers/metricsDeploymentUtils.ts new file mode 100644 index 0000000000..e69de29bb2 From b051d4a7436420bc3662d5ceb2af42c0a0a56fb1 Mon Sep 17 00:00:00 2001 From: ijemmy Date: Mon, 21 Mar 2022 21:28:59 +0100 Subject: [PATCH 02/12] chore(metrics): refactor the decorator test in the same way --- packages/metrics/tests/e2e/constants.ts | 5 + packages/metrics/tests/e2e/decorator.test.ts | 275 +++++++++--------- .../tests/e2e/standardFunctions.test.ts | 33 ++- .../tests/helpers/metricsDeploymentUtils.ts | 0 4 files changed, 166 insertions(+), 147 deletions(-) create mode 100644 packages/metrics/tests/e2e/constants.ts delete mode 100644 packages/metrics/tests/helpers/metricsDeploymentUtils.ts diff --git a/packages/metrics/tests/e2e/constants.ts b/packages/metrics/tests/e2e/constants.ts new file mode 100644 index 0000000000..c89fdfad20 --- /dev/null +++ b/packages/metrics/tests/e2e/constants.ts @@ -0,0 +1,5 @@ +export const RESOURCE_NAME_PREFIX = 'Metrics-E2E'; +export const ONE_MINUTE = 60 * 10_00; +export const TEST_CASE_TIMEOUT = 90_000; // 90 seconds +export const SETUP_TIMEOUT = 300_000; // 300 seconds +export const TEARDOWN_TIMEOUT = 200_000; \ No newline at end of file diff --git a/packages/metrics/tests/e2e/decorator.test.ts b/packages/metrics/tests/e2e/decorator.test.ts index 9397ec221b..b21179b7de 100644 --- a/packages/metrics/tests/e2e/decorator.test.ts +++ b/packages/metrics/tests/e2e/decorator.test.ts @@ -8,28 +8,47 @@ */ import { randomUUID } from 'crypto'; -import * as lambda from '@aws-cdk/aws-lambda-nodejs'; import { Tracing } from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; -import { SdkProvider } from 'aws-cdk/lib/api/aws-auth'; -import { CloudFormationDeployments } from 'aws-cdk/lib/api/cloudformation-deployments'; import * as AWS from 'aws-sdk'; import { MetricUnits } from '../../src'; +import { + ONE_MINUTE, + RESOURCE_NAME_PREFIX, + SETUP_TIMEOUT, + TEARDOWN_TIMEOUT, + TEST_CASE_TIMEOUT +} from './constants'; import { getMetrics } from '../helpers/metricsUtils'; - -const ONE_MINUTE = 1000 * 60; +import { + generateUniqueName, + isValidRuntimeKey, + createStackWithLambdaFunction, + deployStack, + invokeFunction, + destroyStack +} from '@aws-lambda-powertools/commons'; +import path from 'path'; + +const runtime: string = process.env.RUNTIME || 'nodejs14x'; + +if (!isValidRuntimeKey(runtime)) { + throw new Error(`Invalid runtime key value: ${runtime}`); +} + +const uuid = randomUUID(); +const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'decorator'); +const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'decorator'); +const lambdaFunctionCodeFile = 'decorator.test.MyFunction.ts'; const cloudwatchClient = new AWS.CloudWatch(); -const lambdaClient = new AWS.Lambda(); - -const integTestApp = new App(); -const stack = new Stack(integTestApp, 'MetricsE2EDecoratorStack'); -// GIVEN const invocationCount = 2; const startTime = new Date(); -const expectedNamespace = randomUUID(); // to easily find metrics back at assert phase -const expectedServiceName = 'decoratorService'; + +// Parameters to be used by Metrics in the Lambda function +const expectedNamespace = uuid; // to easily find metrics back at assert phase +const expectedServiceName = 'e2eDecorator'; const expectedMetricName = 'MyMetric'; const expectedMetricUnit = MetricUnits.Count; const expectedMetricValue = '1'; @@ -39,139 +58,127 @@ const expectedSingleMetricDimension = { MySingleMetricDim: 'MySingleValue' }; const expectedSingleMetricName = 'MySingleMetric'; const expectedSingleMetricUnit = MetricUnits.Percent; const expectedSingleMetricValue = '2'; -const functionName = 'MyFunctionWithDecoratedHandler'; -new lambda.NodejsFunction(stack, 'MyFunction', { - functionName: functionName, - tracing: Tracing.ACTIVE, - environment: { - EXPECTED_NAMESPACE: expectedNamespace, - EXPECTED_SERVICE_NAME: expectedServiceName, - EXPECTED_METRIC_NAME: expectedMetricName, - EXPECTED_METRIC_UNIT: expectedMetricUnit, - EXPECTED_METRIC_VALUE: expectedMetricValue, - EXPECTED_DEFAULT_DIMENSIONS: JSON.stringify(expectedDefaultDimensions), - EXPECTED_EXTRA_DIMENSION: JSON.stringify(expectedExtraDimension), - EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify(expectedSingleMetricDimension), - EXPECTED_SINGLE_METRIC_NAME: expectedSingleMetricName, - EXPECTED_SINGLE_METRIC_UNIT: expectedSingleMetricUnit, - EXPECTED_SINGLE_METRIC_VALUE: expectedSingleMetricValue, - }, -}); -const stackArtifact = integTestApp.synth().getStackByName(stack.stackName); +const integTestApp = new App(); +let stack: Stack; -describe('happy cases', () => { +describe(`metrics E2E tests (decorator) for runtime: ${runtime}`, () => { beforeAll(async () => { - const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ - profile: process.env.AWS_PROFILE, + // GIVEN a stack + stack = createStackWithLambdaFunction({ + app: integTestApp, + stackName: stackName, + functionName: functionName, + functionEntry: path.join(__dirname, lambdaFunctionCodeFile), + tracing: Tracing.ACTIVE, + environment: { + POWERTOOLS_SERVICE_NAME: 'metrics-e2e-testing', + UUID: uuid, + + // Parameter(s) to be used by Metrics in the Lambda function + EXPECTED_NAMESPACE: expectedNamespace, + EXPECTED_SERVICE_NAME: expectedServiceName, + EXPECTED_METRIC_NAME: expectedMetricName, + EXPECTED_METRIC_UNIT: expectedMetricUnit, + EXPECTED_METRIC_VALUE: expectedMetricValue, + EXPECTED_DEFAULT_DIMENSIONS: JSON.stringify(expectedDefaultDimensions), + EXPECTED_EXTRA_DIMENSION: JSON.stringify(expectedExtraDimension), + EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify(expectedSingleMetricDimension), + EXPECTED_SINGLE_METRIC_NAME: expectedSingleMetricName, + EXPECTED_SINGLE_METRIC_UNIT: expectedSingleMetricUnit, + EXPECTED_SINGLE_METRIC_VALUE: expectedSingleMetricValue, + }, + runtime: runtime, }); - const cloudFormation = new CloudFormationDeployments({ sdkProvider }); - // WHEN - // lambda function is deployed - await cloudFormation.deployStack({ - stack: stackArtifact, - quiet: true, - }); + const stackArtifact = integTestApp.synth().getStackByName(stack.stackName); + await deployStack(stackArtifact); // and invoked - for (let i = 0; i < invocationCount; i++) { - await lambdaClient - .invoke({ - FunctionName: functionName, - }) + await invokeFunction(functionName, invocationCount, 'SEQUENTIAL'); + + }, SETUP_TIMEOUT); + describe('ColdStart metrics', () => { + it('should capture ColdStart Metric', async () => { + const expectedDimensions = [ + { Name: 'service', Value: expectedServiceName }, + { Name: 'function_name', Value: functionName }, + { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, + ]; + // Check coldstart metric dimensions + const coldStartMetrics = await getMetrics(cloudwatchClient, expectedNamespace, 'ColdStart', 1); + + expect(coldStartMetrics.Metrics?.length).toBe(1); + const coldStartMetric = coldStartMetrics.Metrics?.[0]; + expect(coldStartMetric?.Dimensions).toStrictEqual(expectedDimensions); + + // Check coldstart metric value + const adjustedStartTime = new Date(startTime.getTime() - ONE_MINUTE); + const endTime = new Date(new Date().getTime() + ONE_MINUTE); + console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ColdStart --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify(expectedDimensions)}'`); + const coldStartMetricStat = await cloudwatchClient + .getMetricStatistics( + { + Namespace: expectedNamespace, + StartTime: adjustedStartTime, + Dimensions: expectedDimensions, + EndTime: endTime, + Period: 60, + MetricName: 'ColdStart', + Statistics: ['Sum'], + }, + undefined + ) .promise(); - } - }, ONE_MINUTE * 3); - - it('capture ColdStart Metric', async () => { - const expectedDimensions = [ - { Name: 'service', Value: expectedServiceName }, - { Name: 'function_name', Value: functionName }, - { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, - ]; - // Check coldstart metric dimensions - const coldStartMetrics = await getMetrics(cloudwatchClient, expectedNamespace, 'ColdStart', 1); - - expect(coldStartMetrics.Metrics?.length).toBe(1); - const coldStartMetric = coldStartMetrics.Metrics?.[0]; - expect(coldStartMetric?.Dimensions).toStrictEqual(expectedDimensions); - - // Check coldstart metric value - const adjustedStartTime = new Date(startTime.getTime() - 60 * 1000); - const endTime = new Date(new Date().getTime() + 60 * 1000); - console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ColdStart --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify(expectedDimensions)}'`); - const coldStartMetricStat = await cloudwatchClient - .getMetricStatistics( - { - Namespace: expectedNamespace, - StartTime: adjustedStartTime, - Dimensions: expectedDimensions, - EndTime: endTime, - Period: 60, - MetricName: 'ColdStart', - Statistics: ['Sum'], - }, - undefined - ) - .promise(); - - // Despite lambda has been called twice, coldstart metric sum should only be 1 - const singleDataPoint = coldStartMetricStat.Datapoints ? coldStartMetricStat.Datapoints[0] : {}; - expect(singleDataPoint?.Sum).toBe(1); - }, ONE_MINUTE * 3); - - it('produce added Metric with the default and extra one dimensions', async () => { - // Check metric dimensions - const metrics = await getMetrics(cloudwatchClient, expectedNamespace, expectedMetricName, 1); - - expect(metrics.Metrics?.length).toBe(1); - const metric = metrics.Metrics?.[0]; - const expectedDimensions = [ - { Name: 'service', Value: expectedServiceName }, - { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, - { Name: Object.keys(expectedExtraDimension)[0], Value: expectedExtraDimension.MyExtraDimension }, - ]; - expect(metric?.Dimensions).toStrictEqual(expectedDimensions); - - // Check coldstart metric value - const adjustedStartTime = new Date(startTime.getTime() - 3 * ONE_MINUTE); - const endTime = new Date(new Date().getTime() + ONE_MINUTE); - console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ${expectedMetricName} --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify(expectedDimensions)}'`); - const metricStat = await cloudwatchClient - .getMetricStatistics( - { - Namespace: expectedNamespace, - StartTime: adjustedStartTime, - Dimensions: expectedDimensions, - EndTime: endTime, - Period: 60, - MetricName: expectedMetricName, - Statistics: ['Sum'], - }, - undefined - ) - .promise(); - - // Since lambda has been called twice in this test and potentially more in others, metric sum should be at least of expectedMetricValue * invocationCount - const singleDataPoint = metricStat.Datapoints ? metricStat.Datapoints[0] : {}; - expect(singleDataPoint?.Sum).toBeGreaterThanOrEqual(parseInt(expectedMetricValue) * invocationCount); - }, ONE_MINUTE * 3); + // Despite lambda has been called twice, coldstart metric sum should only be 1 + const singleDataPoint = coldStartMetricStat.Datapoints ? coldStartMetricStat.Datapoints[0] : {}; + expect(singleDataPoint?.Sum).toBe(1); + }, TEST_CASE_TIMEOUT); + }); + + describe('Default and extra dimensions', () => { + + it('should produce a Metric with the default and extra one dimensions', async () => { + // Check metric dimensions + const metrics = await getMetrics(cloudwatchClient, expectedNamespace, expectedMetricName, 1); + + expect(metrics.Metrics?.length).toBe(1); + const metric = metrics.Metrics?.[0]; + const expectedDimensions = [ + { Name: 'service', Value: expectedServiceName }, + { Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension }, + { Name: Object.keys(expectedExtraDimension)[0], Value: expectedExtraDimension.MyExtraDimension }, + ]; + expect(metric?.Dimensions).toStrictEqual(expectedDimensions); + + // Check coldstart metric value + const adjustedStartTime = new Date(startTime.getTime() - 3 * ONE_MINUTE); + const endTime = new Date(new Date().getTime() + ONE_MINUTE); + console.log(`Manual command: aws cloudwatch get-metric-statistics --namespace ${expectedNamespace} --metric-name ${expectedMetricName} --start-time ${Math.floor(adjustedStartTime.getTime()/1000)} --end-time ${Math.floor(endTime.getTime()/1000)} --statistics 'Sum' --period 60 --dimensions '${JSON.stringify(expectedDimensions)}'`); + const metricStat = await cloudwatchClient + .getMetricStatistics( + { + Namespace: expectedNamespace, + StartTime: adjustedStartTime, + Dimensions: expectedDimensions, + EndTime: endTime, + Period: 60, + MetricName: expectedMetricName, + Statistics: ['Sum'], + }, + undefined + ) + .promise(); + // Since lambda has been called twice in this test and potentially more in others, metric sum should be at least of expectedMetricValue * invocationCount + const singleDataPoint = metricStat.Datapoints ? metricStat.Datapoints[0] : {}; + expect(singleDataPoint?.Sum).toBeGreaterThanOrEqual(parseInt(expectedMetricValue) * invocationCount); + }, TEST_CASE_TIMEOUT); + }); afterAll(async () => { if (!process.env.DISABLE_TEARDOWN) { - const stackArtifact = integTestApp.synth().getStackByName(stack.stackName); - - const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ - profile: process.env.AWS_PROFILE, - }); - const cloudFormation = new CloudFormationDeployments({ sdkProvider }); - - await cloudFormation.destroyStack({ - stack: stackArtifact, - quiet: true, - }); + await destroyStack(integTestApp, stack); } - }, ONE_MINUTE * 3); + }, TEARDOWN_TIMEOUT); }); diff --git a/packages/metrics/tests/e2e/standardFunctions.test.ts b/packages/metrics/tests/e2e/standardFunctions.test.ts index d6d4c7f12a..138e1674e5 100644 --- a/packages/metrics/tests/e2e/standardFunctions.test.ts +++ b/packages/metrics/tests/e2e/standardFunctions.test.ts @@ -12,8 +12,22 @@ import { Tracing } from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; import * as AWS from 'aws-sdk'; import { MetricUnits } from '../../src'; +import { + ONE_MINUTE, + RESOURCE_NAME_PREFIX, + SETUP_TIMEOUT, + TEARDOWN_TIMEOUT, + TEST_CASE_TIMEOUT +} from './constants'; import { getMetrics } from '../helpers/metricsUtils'; -import { generateUniqueName, isValidRuntimeKey, createStackWithLambdaFunction, deployStack, invokeFunction, destroyStack } from '@aws-lambda-powertools/commons'; +import { + generateUniqueName, + isValidRuntimeKey, + createStackWithLambdaFunction, + deployStack, + invokeFunction, + destroyStack +} from '@aws-lambda-powertools/commons'; import path from 'path'; const runtime: string = process.env.RUNTIME || 'nodejs14x'; @@ -22,25 +36,19 @@ if (!isValidRuntimeKey(runtime)) { throw new Error(`Invalid runtime key value: ${runtime}`); } -const RESOURCE_NAME_PREFIX = 'Metrics-E2E'; -const ONE_MINUTE = 60 * 10_00; -const TEST_CASE_TIMEOUT = 30_000; // 30 seconds -const SETUP_TIMEOUT = 300_000; // 300 seconds -const TEARDOWN_TIMEOUT = 200_000; - -const cloudwatchClient = new AWS.CloudWatch(); - const uuid = randomUUID(); -const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'Decorator'); -const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'Decorator'); +const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'manual'); +const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'manual'); const lambdaFunctionCodeFile = 'standardFunctions.test.MyFunction.ts'; +const cloudwatchClient = new AWS.CloudWatch(); + const invocationCount = 2; const startTime = new Date(); // Parameters to be used by Metrics in the Lambda function const expectedNamespace = uuid; // to easily find metrics back at assert phase -const expectedServiceName = 'MyFunctionWithStandardHandler'; +const expectedServiceName = 'e2eManual'; const expectedMetricName = 'MyMetric'; const expectedMetricUnit = MetricUnits.Count; const expectedMetricValue = '1'; @@ -94,7 +102,6 @@ describe(`metrics E2E tests (manual) for runtime: ${runtime}`, () => { it('should capture ColdStart Metric', async () => { // Check coldstart metric dimensions const coldStartMetrics = await getMetrics(cloudwatchClient, expectedNamespace, 'ColdStart', 1); - expect(coldStartMetrics.Metrics?.length).toBe(1); const coldStartMetric = coldStartMetrics.Metrics?.[0]; expect(coldStartMetric?.Dimensions).toStrictEqual([{ Name: 'service', Value: expectedServiceName }]); diff --git a/packages/metrics/tests/helpers/metricsDeploymentUtils.ts b/packages/metrics/tests/helpers/metricsDeploymentUtils.ts deleted file mode 100644 index e69de29bb2..0000000000 From e612f6bb6320508396f5eb23fcc0a9848801c47c Mon Sep 17 00:00:00 2001 From: ijemmy Date: Tue, 22 Mar 2022 13:56:38 +0100 Subject: [PATCH 03/12] chore(metrics): refactor file name to be consistent --- ...Function.ts => basicFeatures.decorator.test.functionCode.ts} | 0 .../e2e/{decorator.test.ts => basicFeatures.decorators.test.ts} | 2 +- ....MyFunction.ts => basicFeatures.manual.test.functionCode.ts} | 0 .../{standardFunctions.test.ts => basicFeatures.manual.test.ts} | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename packages/metrics/tests/e2e/{decorator.test.MyFunction.ts => basicFeatures.decorator.test.functionCode.ts} (100%) rename packages/metrics/tests/e2e/{decorator.test.ts => basicFeatures.decorators.test.ts} (98%) rename packages/metrics/tests/e2e/{standardFunctions.test.MyFunction.ts => basicFeatures.manual.test.functionCode.ts} (100%) rename packages/metrics/tests/e2e/{standardFunctions.test.ts => basicFeatures.manual.test.ts} (98%) diff --git a/packages/metrics/tests/e2e/decorator.test.MyFunction.ts b/packages/metrics/tests/e2e/basicFeatures.decorator.test.functionCode.ts similarity index 100% rename from packages/metrics/tests/e2e/decorator.test.MyFunction.ts rename to packages/metrics/tests/e2e/basicFeatures.decorator.test.functionCode.ts diff --git a/packages/metrics/tests/e2e/decorator.test.ts b/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts similarity index 98% rename from packages/metrics/tests/e2e/decorator.test.ts rename to packages/metrics/tests/e2e/basicFeatures.decorators.test.ts index b21179b7de..730ea4eb23 100644 --- a/packages/metrics/tests/e2e/decorator.test.ts +++ b/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts @@ -39,7 +39,7 @@ if (!isValidRuntimeKey(runtime)) { const uuid = randomUUID(); const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'decorator'); const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'decorator'); -const lambdaFunctionCodeFile = 'decorator.test.MyFunction.ts'; +const lambdaFunctionCodeFile = 'basicFeatures.decorator.test.functionCode.ts'; const cloudwatchClient = new AWS.CloudWatch(); diff --git a/packages/metrics/tests/e2e/standardFunctions.test.MyFunction.ts b/packages/metrics/tests/e2e/basicFeatures.manual.test.functionCode.ts similarity index 100% rename from packages/metrics/tests/e2e/standardFunctions.test.MyFunction.ts rename to packages/metrics/tests/e2e/basicFeatures.manual.test.functionCode.ts diff --git a/packages/metrics/tests/e2e/standardFunctions.test.ts b/packages/metrics/tests/e2e/basicFeatures.manual.test.ts similarity index 98% rename from packages/metrics/tests/e2e/standardFunctions.test.ts rename to packages/metrics/tests/e2e/basicFeatures.manual.test.ts index 138e1674e5..4b8e8086a6 100644 --- a/packages/metrics/tests/e2e/standardFunctions.test.ts +++ b/packages/metrics/tests/e2e/basicFeatures.manual.test.ts @@ -39,7 +39,7 @@ if (!isValidRuntimeKey(runtime)) { const uuid = randomUUID(); const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'manual'); const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'manual'); -const lambdaFunctionCodeFile = 'standardFunctions.test.MyFunction.ts'; +const lambdaFunctionCodeFile = 'basicFeatures.manual.test.functionCode.ts'; const cloudwatchClient = new AWS.CloudWatch(); From dc39df7404e79184eb52d80a946c6a5c42eeeb95 Mon Sep 17 00:00:00 2001 From: ijemmy Date: Tue, 22 Mar 2022 14:17:35 +0100 Subject: [PATCH 04/12] chore(logger): refactor logger E2E test to use commons/e2eUtil instead of its own --- .../tests/e2e/basicFeatures.middy.test.ts | 26 ++-- .../tests/e2e/childLogger.manual.test.ts | 26 ++-- packages/logger/tests/e2e/constants.ts | 5 + .../tests/e2e/sampleRate.decorator.test.ts | 26 ++-- .../logger/tests/helpers/InvocationLogs.ts | 90 ------------- packages/logger/tests/helpers/e2eUtils.ts | 120 ------------------ 6 files changed, 59 insertions(+), 234 deletions(-) create mode 100644 packages/logger/tests/e2e/constants.ts delete mode 100644 packages/logger/tests/helpers/InvocationLogs.ts delete mode 100644 packages/logger/tests/helpers/e2eUtils.ts diff --git a/packages/logger/tests/e2e/basicFeatures.middy.test.ts b/packages/logger/tests/e2e/basicFeatures.middy.test.ts index fb3976658b..77b848af8f 100644 --- a/packages/logger/tests/e2e/basicFeatures.middy.test.ts +++ b/packages/logger/tests/e2e/basicFeatures.middy.test.ts @@ -10,8 +10,22 @@ import path from 'path'; import { randomUUID } from 'crypto'; import { App, Stack } from '@aws-cdk/core'; -import { createStackWithLambdaFunction, deployStack, destroyStack, generateUniqueName, invokeFunction, isValidRuntimeKey } from '../helpers/e2eUtils'; -import { InvocationLogs } from '../helpers/InvocationLogs'; +import { + createStackWithLambdaFunction, + deployStack, + destroyStack, + generateUniqueName, + InvocationLogs, + invokeFunction, + isValidRuntimeKey +} from '@aws-lambda-powertools/commons'; +import { + RESOURCE_NAME_PREFIX, + STACK_OUTPUT_LOG_GROUP, + SETUP_TIMEOUT, + TEST_CASE_TIMEOUT, + TEARDOWN_TIMEOUT +} from './constants'; const runtime: string = process.env.RUNTIME || 'nodejs14x'; @@ -20,14 +34,10 @@ if (!isValidRuntimeKey(runtime)) { } const LEVEL = InvocationLogs.LEVEL; -const TEST_CASE_TIMEOUT = 20000; // 20 seconds -const SETUP_TIMEOUT = 300000; // 300 seconds -const TEARDOWN_TIMEOUT = 200000; -const STACK_OUTPUT_LOG_GROUP = 'LogGroupName'; const uuid = randomUUID(); -const stackName = generateUniqueName(uuid, runtime, 'BasicFeatures-Middy'); -const functionName = generateUniqueName(uuid, runtime, 'BasicFeatures-Middy'); +const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'BasicFeatures-Middy'); +const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'BasicFeatures-Middy'); const lambdaFunctionCodeFile = 'basicFeatures.middy.test.FunctionCode.ts'; // Text to be used by Logger in the Lambda function diff --git a/packages/logger/tests/e2e/childLogger.manual.test.ts b/packages/logger/tests/e2e/childLogger.manual.test.ts index b8421c41dc..ee20174713 100644 --- a/packages/logger/tests/e2e/childLogger.manual.test.ts +++ b/packages/logger/tests/e2e/childLogger.manual.test.ts @@ -10,8 +10,22 @@ import path from 'path'; import { randomUUID } from 'crypto'; import { App, Stack } from '@aws-cdk/core'; -import { createStackWithLambdaFunction, deployStack, destroyStack, generateUniqueName, invokeFunction, isValidRuntimeKey } from '../helpers/e2eUtils'; -import { InvocationLogs } from '../helpers/InvocationLogs'; +import { + createStackWithLambdaFunction, + deployStack, + destroyStack, + generateUniqueName, + InvocationLogs, + invokeFunction, + isValidRuntimeKey, +} from '@aws-lambda-powertools/commons'; +import { + RESOURCE_NAME_PREFIX, + STACK_OUTPUT_LOG_GROUP, + SETUP_TIMEOUT, + TEST_CASE_TIMEOUT, + TEARDOWN_TIMEOUT +} from './constants'; const runtime: string = process.env.RUNTIME || 'nodejs14x'; @@ -20,14 +34,10 @@ if (!isValidRuntimeKey(runtime)) { } const LEVEL = InvocationLogs.LEVEL; -const TEST_CASE_TIMEOUT = 20000; // 20 seconds -const SETUP_TIMEOUT = 300000; // 300 seconds -const TEARDOWN_TIMEOUT = 200000; -const STACK_OUTPUT_LOG_GROUP = 'LogGroupName'; const uuid = randomUUID(); -const stackName = generateUniqueName(uuid, runtime, 'ChildLogger-Manual'); -const functionName = generateUniqueName(uuid, runtime, 'ChildLogger-Manual'); +const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'ChildLogger-Manual'); +const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'ChildLogger-Manual'); const lambdaFunctionCodeFile = 'childLogger.manual.test.FunctionCode.ts'; // Parameters to be used by Logger in the Lambda function diff --git a/packages/logger/tests/e2e/constants.ts b/packages/logger/tests/e2e/constants.ts new file mode 100644 index 0000000000..d99be1f441 --- /dev/null +++ b/packages/logger/tests/e2e/constants.ts @@ -0,0 +1,5 @@ +export const RESOURCE_NAME_PREFIX = 'Logger-E2E'; +export const TEST_CASE_TIMEOUT = 20_000; // 20 seconds +export const SETUP_TIMEOUT = 300_000; // 300 seconds +export const TEARDOWN_TIMEOUT = 200_000; +export const STACK_OUTPUT_LOG_GROUP = 'LogGroupName'; \ No newline at end of file diff --git a/packages/logger/tests/e2e/sampleRate.decorator.test.ts b/packages/logger/tests/e2e/sampleRate.decorator.test.ts index 8995dc70b7..75ba080588 100644 --- a/packages/logger/tests/e2e/sampleRate.decorator.test.ts +++ b/packages/logger/tests/e2e/sampleRate.decorator.test.ts @@ -10,8 +10,22 @@ import path from 'path'; import { randomUUID } from 'crypto'; import { App, Stack } from '@aws-cdk/core'; -import { createStackWithLambdaFunction, deployStack, destroyStack, generateUniqueName, invokeFunction, isValidRuntimeKey } from '../helpers/e2eUtils'; -import { InvocationLogs } from '../helpers/InvocationLogs'; +import { + createStackWithLambdaFunction, + deployStack, + destroyStack, + generateUniqueName, + InvocationLogs, + invokeFunction, + isValidRuntimeKey +} from '@aws-lambda-powertools/commons'; +import { + RESOURCE_NAME_PREFIX, + STACK_OUTPUT_LOG_GROUP, + SETUP_TIMEOUT, + TEST_CASE_TIMEOUT, + TEARDOWN_TIMEOUT +} from './constants'; const runtime: string = process.env.RUNTIME || 'nodejs14x'; @@ -20,14 +34,10 @@ if (!isValidRuntimeKey(runtime)) { } const LEVEL = InvocationLogs.LEVEL; -const TEST_CASE_TIMEOUT = 30000; // 30 seconds -const SETUP_TIMEOUT = 300000; // 300 seconds -const TEARDOWN_TIMEOUT = 200000; -const STACK_OUTPUT_LOG_GROUP = 'LogGroupName'; const uuid = randomUUID(); -const stackName = generateUniqueName(uuid, runtime, 'SampleRate-Decorator'); -const functionName = generateUniqueName(uuid, runtime, 'SampleRate-Decorator'); +const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'SampleRate-Decorator'); +const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'SampleRate-Decorator'); const lambdaFunctionCodeFile = 'sampleRate.decorator.test.FunctionCode.ts'; // Parameters to be used by Logger in the Lambda function diff --git a/packages/logger/tests/helpers/InvocationLogs.ts b/packages/logger/tests/helpers/InvocationLogs.ts deleted file mode 100644 index fab917ca8d..0000000000 --- a/packages/logger/tests/helpers/InvocationLogs.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Log level. used for filtering the log - */ -export enum LEVEL { - DEBUG = 'DEBUG', - INFO = 'INFO', - WARN = 'WARN', - ERROR = 'ERROR', -} - -export type FunctionLog = { - timestamp: string - invocationId: string - logLevel: LEVEL - // eslint-disable-next-line @typescript-eslint/no-explicit-any - logObject: {[key: string]: any} -}; -export class InvocationLogs { - public static LEVEL = LEVEL; - - /** - * Array of logs from invocation. - * - * The first element is START, and the last two elements are END, and REPORT. - * In each log, each content is separated by '\t' - * [ - * 'START RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678 Version: $LATEST', - * '2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tINFO\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"INFO","message":"This is an INFO log with some context and persistent key","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works"}', - * '2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tINFO\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"INFO","message":"This is an INFO log with some context","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","additionalKey":"additionalValue"}', - * '2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tERROR\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"ERROR","message":"There was an error","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","error":{"name":"Error","location":"/var/task/index.js:2778","message":"you cannot prevent this","stack":"Error: you cannot prevent this\\n at testFunction (/var/task/index.js:2778:11)\\n at runRequest (/var/task/index.js:2314:36)"}}', - * 'END RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678', - * 'REPORT RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678\tDuration: 2.16 ms\tBilled Duration: 3 ms\tMemory Size: 128 MB\tMax Memory Used: 57 MB\t', - * ] - * See https://docs.aws.amazon.com/lambda/latest/dg/nodejs-logging.html for details - */ - private logs: string[]; - - public constructor(logResult: string) { - const rawLog = Buffer.from(logResult, 'base64').toString('utf-8').trim(); - this.logs = rawLog.split('\n'); - } - - /** - * Find all functional logs whether it contains a given key - * @param key - * @param log level to filter - * @returns - */ - public doesAnyFunctionLogsContains(key: string, levelToFilter?: LEVEL): boolean { - const filteredLogs = this.getFunctionLogs(levelToFilter) - .filter(log => log.includes(key)); - - return filteredLogs.length > 0; - } - - /** - * Return only logs from function, excude START, END, and REPORT generated by Lambda service - * @param log level to filter - * @returns Array of function logs - */ - public getFunctionLogs(levelToFilter?: LEVEL): string[] { - let filteredLogs = this.logs.slice(1, -2); - - if (levelToFilter) { - filteredLogs = filteredLogs.filter(log => log.includes(levelToFilter.toString())); - } - - return filteredLogs; - } - - /** - * Each of log message contains 4 elements, separted by tabs - * 1. Timestamp (e.g. 2022-01-27T16:04:39.323Z ) - * 2. Invocation id (e.g. tafb6de1a-48f8-4fbb-ad72-23a4f0c2924c) - * 3. Log level (e.g. INFO) - * 4. Log object (e.g. {\"cold_start\":true, ..}) - * @param message - */ - public static parseFunctionLog(log: string): FunctionLog { - const elements = log.split('\t'); - - return { - timestamp: elements[0], - invocationId: elements[1], - logLevel: elements[2], - logObject: JSON.parse(elements[3]), - }; - } - -} \ No newline at end of file diff --git a/packages/logger/tests/helpers/e2eUtils.ts b/packages/logger/tests/helpers/e2eUtils.ts deleted file mode 100644 index 76e39976d6..0000000000 --- a/packages/logger/tests/helpers/e2eUtils.ts +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 - -import { CloudFormationStackArtifact } from '@aws-cdk/cx-api'; -import { SdkProvider } from 'aws-cdk/lib/api/aws-auth'; -import { CloudFormationDeployments } from 'aws-cdk/lib/api/cloudformation-deployments'; -import { App, CfnOutput, Stack } from '@aws-cdk/core'; -import * as lambda from '@aws-cdk/aws-lambda-nodejs'; -import { Runtime } from '@aws-cdk/aws-lambda'; -import * as AWS from 'aws-sdk'; - -import { InvocationLogs } from './InvocationLogs'; - -const lambdaClient = new AWS.Lambda(); - -const NAME_PREFIX = 'Logger-E2E'; -const testRuntimeKeys = [ 'nodejs12x', 'nodejs14x' ]; -export type TestRuntimesKey = typeof testRuntimeKeys[number]; -const TEST_RUNTIMES: Record = { - nodejs12x: Runtime.NODEJS_12_X, - nodejs14x: Runtime.NODEJS_14_X, -}; - -export type StackWithLambdaFunctionOptions = { - app: App - stackName: string - functionName: string - functionEntry: string - environment: {[key: string]: string} - logGroupOutputKey: string - runtime: string -}; - -export const isValidRuntimeKey = (runtime: string): runtime is TestRuntimesKey => testRuntimeKeys.includes(runtime); - -export const createStackWithLambdaFunction = (params: StackWithLambdaFunctionOptions): Stack => { - - const stack = new Stack(params.app, params.stackName); - const testFunction = new lambda.NodejsFunction(stack, `testFunction`, { - functionName: params.functionName, - entry: params.functionEntry, - environment: params.environment, - runtime: TEST_RUNTIMES[params.runtime as TestRuntimesKey], - }); - - new CfnOutput(stack, params.logGroupOutputKey, { - value: testFunction.logGroup.logGroupName, - }); - - return stack; -}; - -export const generateUniqueName = (uuid: string, runtime: string, testName: string): string => - `${NAME_PREFIX}-${runtime}-${testName}-${uuid}`.substring(0, 64); - -export const deployStack = async (stackArtifact: CloudFormationStackArtifact ): Promise<{[name:string]: string}> => { - const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ - profile: process.env.AWS_PROFILE, - }); - const cloudFormation = new CloudFormationDeployments({ sdkProvider }); - - // WHEN lambda function is deployed - const result = await cloudFormation.deployStack({ - stack: stackArtifact, - quiet: true, - }); - - return result.outputs; -}; - -export const invokeFunction = async (functionName: string, times: number = 1, invocationMode: 'PARALLEL' | 'SEQUENTIAL' = 'PARALLEL'): Promise => { - const invocationLogs: InvocationLogs[] = []; - - const promiseFactory = () : Promise => { - const invokePromise = lambdaClient - .invoke({ - FunctionName: functionName, - LogType: 'Tail', // Wait until execution completes and return all logs - }) - .promise() - .then((response) => { - if (response?.LogResult) { - invocationLogs.push(new InvocationLogs(response?.LogResult)); - } else { - throw new Error('No LogResult field returned in the response of Lambda invocation. This should not happen.'); - } - }); - - return invokePromise; - }; - - const promiseFactories = Array.from({ length: times }, () => promiseFactory ); - const invocation = invocationMode == 'PARALLEL' - ? Promise.all(promiseFactories.map(factory => factory())) - : chainPromises(promiseFactories); - await invocation; - - return invocationLogs; -}; - -export const destroyStack = async (app: App, stack: Stack): Promise => { - const stackArtifact = app.synth().getStackByName(stack.stackName); - - const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ - profile: process.env.AWS_PROFILE, - }); - const cloudFormation = new CloudFormationDeployments({ sdkProvider }); - - await cloudFormation.destroyStack({ - stack: stackArtifact, - quiet: true, - }); -}; - -const chainPromises = async (promiseFactories: (() => Promise)[]) : Promise => { - let chain = Promise.resolve(); - promiseFactories.forEach(factory => chain = chain.then(factory)); - - return chain; -}; From a45f0542912d264cab1ce37441ca962556569834 Mon Sep 17 00:00:00 2001 From: ijemmy Date: Tue, 22 Mar 2022 14:26:54 +0100 Subject: [PATCH 05/12] test(metrics): run e2e tests on both Node12 and Node14 runtime --- packages/metrics/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/metrics/package.json b/packages/metrics/package.json index 68e5fcd93a..00bb2934f7 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -13,7 +13,9 @@ "commit": "commit", "test": "npm run test:unit", "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", - "test:e2e": "jest --group=e2e", + "test:e2e:nodejs12x": "RUNTIME=nodejs12x jest --group=e2e/logger", + "test:e2e:nodejs14x": "RUNTIME=nodejs14x jest --group=e2e/logger", + "test:e2e": "concurrently \"npm:test:e2e:nodejs12x\" \"npm:test:e2e:nodejs14x\"", "watch": "jest --group=unit --watch ", "build": "tsc", "lint": "eslint --ext .ts --fix --no-error-on-unmatched-pattern src tests", From 7ca572e4836c00af763c3c4b80e20011f7cfa633 Mon Sep 17 00:00:00 2001 From: ijemmy Date: Tue, 22 Mar 2022 16:54:14 +0100 Subject: [PATCH 06/12] test(commons): move e2eUtils to an appropriate folder and write unit tests for InvocationLogs --- packages/commons/jest.config.js | 6 + packages/commons/src/index.ts | 2 +- .../{utils => tests}/e2e/InvocationLogs.ts | 12 +- .../src/{utils => tests}/e2e/e2eUtils.ts | 6 + .../commons/src/{utils => tests}/e2e/index.ts | 0 .../commons/tests/unit/InvocationLogs.test.ts | 138 ++++++++++++++++++ 6 files changed, 157 insertions(+), 7 deletions(-) rename packages/commons/src/{utils => tests}/e2e/InvocationLogs.ts (91%) rename packages/commons/src/{utils => tests}/e2e/e2eUtils.ts (96%) rename packages/commons/src/{utils => tests}/e2e/index.ts (100%) create mode 100644 packages/commons/tests/unit/InvocationLogs.test.ts diff --git a/packages/commons/jest.config.js b/packages/commons/jest.config.js index 6fbaae512f..7f0964f02f 100644 --- a/packages/commons/jest.config.js +++ b/packages/commons/jest.config.js @@ -19,6 +19,12 @@ module.exports = { 'testEnvironment': 'node', 'coveragePathIgnorePatterns': [ '/node_modules/', + /** + * e2eUtils.ts contain helper methods that simplify several calls to CDK and SDK interface. + * Unit testing it is mostly mocking the CDK/SDK functions. It will be brittle and yield little value. + * In addition, the file is used for every E2E test run, so its correctness is verified for every PR. + */ + '/src/tests/e2e/e2eUtils.ts', ], 'coverageThreshold': { 'global': { diff --git a/packages/commons/src/index.ts b/packages/commons/src/index.ts index 9b6f264d81..85e8b59b42 100644 --- a/packages/commons/src/index.ts +++ b/packages/commons/src/index.ts @@ -1,5 +1,5 @@ export * from './utils/lambda'; -export * from './utils/e2e'; export * from './Utility'; +export * from './tests/e2e'; export * as ContextExamples from './tests/resources/contexts'; export * as Events from './tests/resources/events'; \ No newline at end of file diff --git a/packages/commons/src/utils/e2e/InvocationLogs.ts b/packages/commons/src/tests/e2e/InvocationLogs.ts similarity index 91% rename from packages/commons/src/utils/e2e/InvocationLogs.ts rename to packages/commons/src/tests/e2e/InvocationLogs.ts index fab917ca8d..f220581090 100644 --- a/packages/commons/src/utils/e2e/InvocationLogs.ts +++ b/packages/commons/src/tests/e2e/InvocationLogs.ts @@ -41,20 +41,20 @@ export class InvocationLogs { } /** - * Find all functional logs whether it contains a given key - * @param key + * Find all functional logs whether it contains a given text + * @param text * @param log level to filter * @returns */ - public doesAnyFunctionLogsContains(key: string, levelToFilter?: LEVEL): boolean { + public doesAnyFunctionLogsContains(text: string, levelToFilter?: LEVEL): boolean { const filteredLogs = this.getFunctionLogs(levelToFilter) - .filter(log => log.includes(key)); + .filter(log => log.includes(text)); return filteredLogs.length > 0; } /** - * Return only logs from function, excude START, END, and REPORT generated by Lambda service + * Return only logs from function, exclude START, END, and REPORT generated by Lambda service * @param log level to filter * @returns Array of function logs */ @@ -62,7 +62,7 @@ export class InvocationLogs { let filteredLogs = this.logs.slice(1, -2); if (levelToFilter) { - filteredLogs = filteredLogs.filter(log => log.includes(levelToFilter.toString())); + filteredLogs = filteredLogs.filter(log => log.split('\t')[2] == levelToFilter); } return filteredLogs; diff --git a/packages/commons/src/utils/e2e/e2eUtils.ts b/packages/commons/src/tests/e2e/e2eUtils.ts similarity index 96% rename from packages/commons/src/utils/e2e/e2eUtils.ts rename to packages/commons/src/tests/e2e/e2eUtils.ts index f455d9b07e..edf47a104e 100644 --- a/packages/commons/src/utils/e2e/e2eUtils.ts +++ b/packages/commons/src/tests/e2e/e2eUtils.ts @@ -1,6 +1,12 @@ +/* istanbul ignore file */ + // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT-0 +/** + * E2E utils is used by e2e tests. They are helper function that calls either CDK or SDK + * to interact with services. +*/ import { CloudFormationStackArtifact } from '@aws-cdk/cx-api'; import { SdkProvider } from 'aws-cdk/lib/api/aws-auth'; import { CloudFormationDeployments } from 'aws-cdk/lib/api/cloudformation-deployments'; diff --git a/packages/commons/src/utils/e2e/index.ts b/packages/commons/src/tests/e2e/index.ts similarity index 100% rename from packages/commons/src/utils/e2e/index.ts rename to packages/commons/src/tests/e2e/index.ts diff --git a/packages/commons/tests/unit/InvocationLogs.test.ts b/packages/commons/tests/unit/InvocationLogs.test.ts new file mode 100644 index 0000000000..74d193ecf0 --- /dev/null +++ b/packages/commons/tests/unit/InvocationLogs.test.ts @@ -0,0 +1,138 @@ +/** + * Test InvocationLogs class + * + * @group unit/commons/invocationLogs + * + */ + +import { InvocationLogs, LEVEL } from '../../src'; + +const exampleLogs = `START RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678 Version: $LATEST +2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tDEBUG\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"DEBUG","message":"This is a DEBUG log but contains the word INFO some context and persistent key","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works"} +2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tINFO\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"INFO","message":"This is an INFO log with some context","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","additionalKey":"additionalValue"} +2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tINFO\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"INFO","message":"This is a second INFO log with some context","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","additionalKey":"additionalValue"} +2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tERROR\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"ERROR","message":"There was an error","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","error":{"name":"Error","location":"/var/task/index.js:2778","message":"you cannot prevent this","stack":"Error: you cannot prevent this\\n at testFunction (/var/task/index.js:2778:11)\\n at runRequest (/var/task/index.js:2314:36)"}} +END RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678 +REPORT RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678\tDuration: 2.16 ms\tBilled Duration: 3 ms\tMemory Size: 128 MB\tMax Memory Used: 57 MB\t`; + +describe('Constructor', () => { + test('it should parse base64 text correctly', () => { + const invocationLogs = new InvocationLogs(Buffer.from(exampleLogs).toString('base64')); + expect(invocationLogs.getFunctionLogs(LEVEL.DEBUG).length).toBe(1); + expect(invocationLogs.getFunctionLogs(LEVEL.INFO).length).toBe(2); + expect(invocationLogs.getFunctionLogs(LEVEL.ERROR).length).toBe(1); + }); + +}); + +describe('doesAnyFunctionLogsContains()', () => { + let invocationLogs: InvocationLogs; + + beforeEach(() => { + invocationLogs = new InvocationLogs(Buffer.from(exampleLogs).toString('base64')); + }); + test('it should return true if the text appear in any logs', () => { + const phraseInMessage = 'This is'; + expect(invocationLogs.doesAnyFunctionLogsContains(phraseInMessage)).toBe(true); + + }); + test('it should return false if the text does not appear in any logs', () => { + const phraseNotInMessage = 'A quick brown fox jumps over the lazy dog'; + expect(invocationLogs.doesAnyFunctionLogsContains(phraseNotInMessage)).toBe(false); + }); + + test('it should return true for key in the log', () => { + const keyInLog = 'error'; + expect(invocationLogs.doesAnyFunctionLogsContains(keyInLog)).toBe(true); + }); + + test('it should return true for a text in an error key', () => { + const textInError = '/var/task/index.js:2778'; + expect(invocationLogs.doesAnyFunctionLogsContains(textInError)).toBe(true); + }); + test('it should return false for the text that appears only on the ', () => { + const textInStartLine = 'Version: $LATEST'; + const textInEndLine = 'END RequestId'; + const textInReportLine = 'Billed Duration'; + expect(invocationLogs.doesAnyFunctionLogsContains(textInStartLine)).toBe(false); + expect(invocationLogs.doesAnyFunctionLogsContains(textInEndLine)).toBe(false); + expect(invocationLogs.doesAnyFunctionLogsContains(textInReportLine)).toBe(false); + }); + + test('it should apply filter log based on the given level', () => { + const debugLogHasWordINFO = invocationLogs.doesAnyFunctionLogsContains('INFO', LEVEL.DEBUG); + expect(debugLogHasWordINFO).toBe(true); + + const infoLogHasWordINFO = invocationLogs.doesAnyFunctionLogsContains('INFO', LEVEL.INFO); + expect(infoLogHasWordINFO).toBe(true); + + const errorLogHasWordINFO = invocationLogs.doesAnyFunctionLogsContains('INFO', LEVEL.ERROR); + expect(errorLogHasWordINFO).toBe(false); + + }); +}); + +describe('getFunctionLogs()', () => { + let invocationLogs: InvocationLogs; + + beforeEach(() => { + invocationLogs = new InvocationLogs(Buffer.from(exampleLogs).toString('base64')); + }); + + test('it should retrive logs of the given level only', () => { + const infoLogs = invocationLogs.getFunctionLogs(LEVEL.INFO); + expect(infoLogs.length).toBe(2); + expect(infoLogs[0].includes('INFO')).toBe(true); + expect(infoLogs[1].includes('INFO')).toBe(true); + expect(infoLogs[0].includes('ERROR')).toBe(false); + expect(infoLogs[1].includes('ERROR')).toBe(false); + + const errorLogs = invocationLogs.getFunctionLogs(LEVEL.ERROR); + expect(errorLogs.length).toBe(1); + expect(errorLogs[0].includes('INFO')).toBe(false); + expect(errorLogs[0].includes('ERROR')).toBe(true); + }); + + test('it should NOT return logs generated by Lambda service (e.g. START, END, and REPORT)', () => { + const errorLogs = invocationLogs.getFunctionLogs(LEVEL.ERROR); + expect(errorLogs.length).toBe(1); + expect(errorLogs[0].includes('START')).toBe(false); + expect(errorLogs[0].includes('END')).toBe(false); + expect(errorLogs[0].includes('REPORT')).toBe(false); + }); +}); + +describe('parseFunctionLog()', () => { + test('it should return object with the correct values based on the given log', () => { + const rawLogStr = '2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tDEBUG\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"DEBUG","message":"This is a DEBUG log but contains the word INFO some context and persistent key","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works"}'; + + const logObj = InvocationLogs.parseFunctionLog(rawLogStr); + expect(logObj.timestamp).toBe('2022-01-27T16:04:39.323Z'); + expect(logObj.invocationId).toBe('c6af9ac6-7b61-11e6-9a41-93e812345678'); + expect(logObj.logLevel).toBe(LEVEL.DEBUG); + expect(logObj.logObject).toStrictEqual({ + cold_start: true, + function_arn: 'arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c', + function_memory_size: 128, + function_name: 'loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c', + function_request_id: '7f586697-238a-4c3b-9250-a5f057c1119c', + level: 'DEBUG', + message: 'This is a DEBUG log but contains the word INFO some context and persistent key', + service: 'logger-e2e-testing', + timestamp: '2022-01-27T16:04:39.323Z', + persistentKey: 'works', + }); + }); + + test('it should throw an error if receive incorrect formatted raw log string', () => { + const noTabString = 'no-tab-character'; + expect(() => { + InvocationLogs.parseFunctionLog(noTabString); + }).toThrow(Error); + + const missSomeElements = '2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tDEBUG\t'; + expect(() => { + InvocationLogs.parseFunctionLog(missSomeElements); + }).toThrow(Error); + }); +}); \ No newline at end of file From 4e0c6b7caf7a0e5dfff1fb8c52a4a6678b77e0a4 Mon Sep 17 00:00:00 2001 From: ijemmy Date: Wed, 23 Mar 2022 10:04:26 +0100 Subject: [PATCH 07/12] Update packages/commons/jest.config.js Co-authored-by: Andrea Amorosi --- packages/commons/jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/commons/jest.config.js b/packages/commons/jest.config.js index 7f0964f02f..f19a225a9e 100644 --- a/packages/commons/jest.config.js +++ b/packages/commons/jest.config.js @@ -20,7 +20,7 @@ module.exports = { 'coveragePathIgnorePatterns': [ '/node_modules/', /** - * e2eUtils.ts contain helper methods that simplify several calls to CDK and SDK interface. + * e2eUtils.ts contains helper methods that simplify several calls to CDK and SDK interface. * Unit testing it is mostly mocking the CDK/SDK functions. It will be brittle and yield little value. * In addition, the file is used for every E2E test run, so its correctness is verified for every PR. */ From f9915f0be27150b487fc06dce83b01407ce83583 Mon Sep 17 00:00:00 2001 From: ijemmy Date: Wed, 23 Mar 2022 15:39:21 +0100 Subject: [PATCH 08/12] test(logger,metrics): move e2eUtils into the same folder as cdk-cli and don't expose it outside of the package --- packages/commons/src/index.ts | 1 - packages/commons/src/tests/e2e/index.ts | 2 -- .../tests/e2e => tests/utils}/InvocationLogs.ts | 0 .../{src/tests/e2e => tests/utils}/e2eUtils.ts | 0 .../logger/tests/e2e/basicFeatures.middy.test.ts | 4 ++-- .../logger/tests/e2e/childLogger.manual.test.ts | 4 ++-- .../tests/e2e/sampleRate.decorator.test.ts | 4 ++-- .../tests/e2e/basicFeatures.decorators.test.ts | 16 ++++++++-------- .../tests/e2e/basicFeatures.manual.test.ts | 16 ++++++++-------- 9 files changed, 22 insertions(+), 25 deletions(-) delete mode 100644 packages/commons/src/tests/e2e/index.ts rename packages/commons/{src/tests/e2e => tests/utils}/InvocationLogs.ts (100%) rename packages/commons/{src/tests/e2e => tests/utils}/e2eUtils.ts (100%) diff --git a/packages/commons/src/index.ts b/packages/commons/src/index.ts index 85e8b59b42..1a056dbf93 100644 --- a/packages/commons/src/index.ts +++ b/packages/commons/src/index.ts @@ -1,5 +1,4 @@ export * from './utils/lambda'; export * from './Utility'; -export * from './tests/e2e'; export * as ContextExamples from './tests/resources/contexts'; export * as Events from './tests/resources/events'; \ No newline at end of file diff --git a/packages/commons/src/tests/e2e/index.ts b/packages/commons/src/tests/e2e/index.ts deleted file mode 100644 index fb411a13a3..0000000000 --- a/packages/commons/src/tests/e2e/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './e2eUtils'; -export * from './InvocationLogs'; \ No newline at end of file diff --git a/packages/commons/src/tests/e2e/InvocationLogs.ts b/packages/commons/tests/utils/InvocationLogs.ts similarity index 100% rename from packages/commons/src/tests/e2e/InvocationLogs.ts rename to packages/commons/tests/utils/InvocationLogs.ts diff --git a/packages/commons/src/tests/e2e/e2eUtils.ts b/packages/commons/tests/utils/e2eUtils.ts similarity index 100% rename from packages/commons/src/tests/e2e/e2eUtils.ts rename to packages/commons/tests/utils/e2eUtils.ts diff --git a/packages/logger/tests/e2e/basicFeatures.middy.test.ts b/packages/logger/tests/e2e/basicFeatures.middy.test.ts index 12a269783c..bbf31c228b 100644 --- a/packages/logger/tests/e2e/basicFeatures.middy.test.ts +++ b/packages/logger/tests/e2e/basicFeatures.middy.test.ts @@ -13,10 +13,10 @@ import { App, Stack } from 'aws-cdk-lib'; import { createStackWithLambdaFunction, generateUniqueName, - InvocationLogs, invokeFunction, isValidRuntimeKey -} from '@aws-lambda-powertools/commons'; +} from '../../../commons/tests/utils/e2eUtils'; +import { InvocationLogs } from '../../../commons/tests/utils/InvocationLogs'; import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; import { RESOURCE_NAME_PREFIX, diff --git a/packages/logger/tests/e2e/childLogger.manual.test.ts b/packages/logger/tests/e2e/childLogger.manual.test.ts index 45dda80a0a..8f821663c6 100644 --- a/packages/logger/tests/e2e/childLogger.manual.test.ts +++ b/packages/logger/tests/e2e/childLogger.manual.test.ts @@ -13,10 +13,10 @@ import { App, Stack } from 'aws-cdk-lib'; import { createStackWithLambdaFunction, generateUniqueName, - InvocationLogs, invokeFunction, isValidRuntimeKey -} from '@aws-lambda-powertools/commons'; +} from '../../../commons/tests/utils/e2eUtils'; +import { InvocationLogs } from '../../../commons/tests/utils/InvocationLogs'; import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; import { RESOURCE_NAME_PREFIX, diff --git a/packages/logger/tests/e2e/sampleRate.decorator.test.ts b/packages/logger/tests/e2e/sampleRate.decorator.test.ts index b3c102ec65..ac0b2e83dc 100644 --- a/packages/logger/tests/e2e/sampleRate.decorator.test.ts +++ b/packages/logger/tests/e2e/sampleRate.decorator.test.ts @@ -13,10 +13,10 @@ import { App, Stack } from 'aws-cdk-lib'; import { createStackWithLambdaFunction, generateUniqueName, - InvocationLogs, invokeFunction, isValidRuntimeKey -} from '@aws-lambda-powertools/commons'; +} from '../../../commons/tests/utils/e2eUtils'; +import { InvocationLogs } from '../../../commons/tests/utils/InvocationLogs'; import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; import { RESOURCE_NAME_PREFIX, diff --git a/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts b/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts index 8e5934565b..c23bb78086 100644 --- a/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts +++ b/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts @@ -7,11 +7,18 @@ * @group e2e/metrics/decorator */ +import path from 'path'; import { randomUUID } from 'crypto'; import { Tracing } from 'aws-cdk-lib/aws-lambda'; import { App, Stack } from 'aws-cdk-lib'; -import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; import * as AWS from 'aws-sdk'; +import { + generateUniqueName, + isValidRuntimeKey, + createStackWithLambdaFunction, + invokeFunction, +} from '../../../commons/tests/utils/e2eUtils'; +import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; import { MetricUnits } from '../../src'; import { ONE_MINUTE, @@ -21,13 +28,6 @@ import { TEST_CASE_TIMEOUT } from './constants'; import { getMetrics } from '../helpers/metricsUtils'; -import { - generateUniqueName, - isValidRuntimeKey, - createStackWithLambdaFunction, - invokeFunction, -} from '@aws-lambda-powertools/commons'; -import path from 'path'; const runtime: string = process.env.RUNTIME || 'nodejs14x'; diff --git a/packages/metrics/tests/e2e/basicFeatures.manual.test.ts b/packages/metrics/tests/e2e/basicFeatures.manual.test.ts index 3e26c29589..56548fac78 100644 --- a/packages/metrics/tests/e2e/basicFeatures.manual.test.ts +++ b/packages/metrics/tests/e2e/basicFeatures.manual.test.ts @@ -7,11 +7,18 @@ * @group e2e/metrics/standardFunctions */ +import path from 'path'; import { randomUUID } from 'crypto'; import { Tracing } from 'aws-cdk-lib/aws-lambda'; import { App, Stack } from 'aws-cdk-lib'; -import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; import * as AWS from 'aws-sdk'; +import { + generateUniqueName, + isValidRuntimeKey, + createStackWithLambdaFunction, + invokeFunction, +} from '../../../commons/tests/utils/e2eUtils'; +import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; import { MetricUnits } from '../../src'; import { ONE_MINUTE, @@ -21,13 +28,6 @@ import { TEST_CASE_TIMEOUT } from './constants'; import { getMetrics } from '../helpers/metricsUtils'; -import { - generateUniqueName, - isValidRuntimeKey, - createStackWithLambdaFunction, - invokeFunction, -} from '@aws-lambda-powertools/commons'; -import path from 'path'; const runtime: string = process.env.RUNTIME || 'nodejs14x'; From 8e312737fbe69f2c2ae3703e274dece9e43ebad6 Mon Sep 17 00:00:00 2001 From: ijemmy Date: Wed, 23 Mar 2022 15:47:20 +0100 Subject: [PATCH 09/12] test(logger,metrics); update test:e2e target to not be specific to package --- packages/logger/package.json | 4 ++-- packages/metrics/package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/logger/package.json b/packages/logger/package.json index ca81589b4f..9974c7f602 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -13,8 +13,8 @@ "commit": "commit", "test": "npm run test:unit", "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", - "test:e2e:nodejs12x": "RUNTIME=nodejs12x jest --group=e2e/logger", - "test:e2e:nodejs14x": "RUNTIME=nodejs14x jest --group=e2e/logger", + "test:e2e:nodejs12x": "RUNTIME=nodejs12x jest --group=e2e", + "test:e2e:nodejs14x": "RUNTIME=nodejs14x jest --group=e2e", "test:e2e": "concurrently \"npm:test:e2e:nodejs12x\" \"npm:test:e2e:nodejs14x\"", "watch": "jest --watch --group=unit", "build": "tsc", diff --git a/packages/metrics/package.json b/packages/metrics/package.json index 00bb2934f7..703ce5b5b4 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -13,8 +13,8 @@ "commit": "commit", "test": "npm run test:unit", "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", - "test:e2e:nodejs12x": "RUNTIME=nodejs12x jest --group=e2e/logger", - "test:e2e:nodejs14x": "RUNTIME=nodejs14x jest --group=e2e/logger", + "test:e2e:nodejs12x": "RUNTIME=nodejs12x jest --group=e2e", + "test:e2e:nodejs14x": "RUNTIME=nodejs14x jest --group=e2e", "test:e2e": "concurrently \"npm:test:e2e:nodejs12x\" \"npm:test:e2e:nodejs14x\"", "watch": "jest --group=unit --watch ", "build": "tsc", From cfb3931e9e7d1da14672700a9464fd8abb5b3be8 Mon Sep 17 00:00:00 2001 From: ijemmy Date: Wed, 23 Mar 2022 15:49:21 +0100 Subject: [PATCH 10/12] test(commons): fix unit test for InvocationLogs --- packages/commons/jest.config.js | 6 ------ packages/commons/tests/unit/InvocationLogs.test.ts | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/commons/jest.config.js b/packages/commons/jest.config.js index f19a225a9e..6fbaae512f 100644 --- a/packages/commons/jest.config.js +++ b/packages/commons/jest.config.js @@ -19,12 +19,6 @@ module.exports = { 'testEnvironment': 'node', 'coveragePathIgnorePatterns': [ '/node_modules/', - /** - * e2eUtils.ts contains helper methods that simplify several calls to CDK and SDK interface. - * Unit testing it is mostly mocking the CDK/SDK functions. It will be brittle and yield little value. - * In addition, the file is used for every E2E test run, so its correctness is verified for every PR. - */ - '/src/tests/e2e/e2eUtils.ts', ], 'coverageThreshold': { 'global': { diff --git a/packages/commons/tests/unit/InvocationLogs.test.ts b/packages/commons/tests/unit/InvocationLogs.test.ts index 74d193ecf0..1ef5e33cc2 100644 --- a/packages/commons/tests/unit/InvocationLogs.test.ts +++ b/packages/commons/tests/unit/InvocationLogs.test.ts @@ -5,7 +5,7 @@ * */ -import { InvocationLogs, LEVEL } from '../../src'; +import { InvocationLogs, LEVEL } from '../utils/InvocationLogs'; const exampleLogs = `START RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678 Version: $LATEST 2022-01-27T16:04:39.323Z\tc6af9ac6-7b61-11e6-9a41-93e812345678\tDEBUG\t{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"DEBUG","message":"This is a DEBUG log but contains the word INFO some context and persistent key","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works"} From 58899f52cfe4ef6642b1a9438e7a10e1341fe4d5 Mon Sep 17 00:00:00 2001 From: ijemmy Date: Wed, 23 Mar 2022 15:55:34 +0100 Subject: [PATCH 11/12] chore(commons): remove istanbul ignore line --- packages/commons/tests/utils/e2eUtils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/commons/tests/utils/e2eUtils.ts b/packages/commons/tests/utils/e2eUtils.ts index 097b7a9c9b..00143f4788 100644 --- a/packages/commons/tests/utils/e2eUtils.ts +++ b/packages/commons/tests/utils/e2eUtils.ts @@ -1,5 +1,3 @@ -/* istanbul ignore file */ - // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT-0 From 6a2dd7d0dc5f171bc090bdc6bea63414c5328a2d Mon Sep 17 00:00:00 2001 From: ijemmy Date: Wed, 23 Mar 2022 16:07:46 +0100 Subject: [PATCH 12/12] doc(all): update CONTRIBUTING.md to refer to v2 --- CONTRIBUTING.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40eabd9c71..301a291d15 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,7 +101,7 @@ You can run each group separately or all together thanks to [jest-runner-groups] Unit tests, under `tests/unit` folder, are standard [Jest](https://jestjs.io) tests. -End-to-end tests, under `tests/e2e` folder, will test the module features by deploying AWS Lambda functions into your AWS Account. We use [aws-cdk](https://docs.aws.amazon.com/cdk/v1/guide/getting_started.html) v1 library (not v2 due to [this cdk issue](https://github.com/aws/aws-cdk/issues/18211)) for TypeScript for creating infrastructure, and [aws-sdk-js v2](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/) for invoking the functions and asserting on the expected behaviors. All steps are also executed by Jest. +End-to-end tests, under `tests/e2e` folder, will test the module features by deploying AWS Lambda functions into your AWS Account. We use [aws-cdk](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) v2 library with TypeScript for creating infrastructure, and [aws-sdk-js v2](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/) for invoking the functions and asserting on the expected behaviors. All steps are also executed by Jest. Running end-to-end tests will deploy AWS resources. You will need an AWS account, and the tests might incur costs. The cost from **some services** are covered by the [AWS Free Tier](https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all) but not all of them. If you don't have an AWS Account, follow [these instructions to create one](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/). @@ -133,7 +133,7 @@ To run unit tests, you can either use: ##### Set up -We create e2e testing infrastructure with CDK. If you have never used it before, please check its [Getting started guide](https://docs.aws.amazon.com/cdk/v1/guide/getting_started.html). You need to run `cdk bootstrap` in the account and region you are going to run e2e tests in. +We create e2e testing infrastructure with CDK. If you have never used it before, please check its [Getting started guide](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html). You need to run `cdk bootstrap` in the account and region you are going to run e2e tests in. ##### Write @@ -147,7 +147,9 @@ As mentioned in the previous section, tests are split into groups thanks to [jes */ ``` - See `metrics/tests/e2e/decorator.test.ts` as an example. +Follow this convention for the test filename: `..test.ts`. (e.g. `sampleRate.decorator.test.ts`, `childLogger.manual.test.ts`) + +See `metrics/tests/e2e/basicFeatures.decorator.test.ts` as an example. ##### Run