Skip to content

Commit 2b6987a

Browse files
authored
test(logger): test logger does not format objects #6083
## Problem The test logger does not format additional arguements into the logs in the same way as ToolkitLogger. Ex. `getLogger().debug('here is the obj: %O', obj)` will produce two `LogEntries` one with `"here is the obj: %O"` and one with the object itself. This is problematic because it causes confusion (see: #5895 (comment)) and it also can cause `assertLogsContain` to throw an error since there is now a non-string/error entry in the log entry list (see: https://github.com/aws/aws-toolkit-vscode/blob/338ea67f2ba0294fc535a9a949fd1cdaeaa96d98/packages/core/src/test/globalSetup.test.ts#L171-L185). ## Solution - Have test logger inherit from `BaseLogger` to minimize implementation dupe. - If we see a string, format it with extra inputs before pushing it into entries. - Add tests for the `testLogger`. - Adjust existing tests that relied on this behavior. - If on web, we simply concat the strings to avoid reimplementing `util.format`: https://github.com/nodejs/node/blob/3178a762d6a2b1a37b74f02266eea0f3d86603be/lib/internal/util/inspect.js#L2191-L2315 --- <!--- REMINDER: Ensure that your PR meets the guidelines in CONTRIBUTING.md --> License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 0eb9012 commit 2b6987a

File tree

7 files changed

+97
-49
lines changed

7 files changed

+97
-49
lines changed

packages/core/src/test/awsService/appBuilder/explorer/samProject.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,21 @@ describe('samProject', () => {
6464
assert.equal(region, undefined)
6565
})
6666

67-
it('returns {} when (unlikely) given an undefind project root uri', async () => {
67+
it('returns {} when (unlikely) given an undefined project root uri', async () => {
6868
const wrapperCall = async (projectRootUri: any) => {
6969
return await getStackName(projectRootUri)
7070
}
7171

7272
const result = await wrapperCall(undefined)
7373
assert.deepStrictEqual(result, {})
74-
assertLogsContain('Error getting stack name or region information: No project folder found', true, 'warn')
74+
assertLogsContain('Error getting stack name or region information: No project folder found', false, 'warn')
7575
})
7676

7777
it('returns empty object give no samconfig file found', async () => {
7878
// simulate error when no samconfig.toml file in directory
7979
const result = await getStackName(projectRoot)
8080
assert.deepStrictEqual(result, {})
81-
assertLogsContain('No stack name or region information available in samconfig.toml: %O', true, 'info')
81+
assertLogsContain('No stack name or region information available in samconfig.toml', false, 'info')
8282
})
8383

8484
it('returns empty object give error parsing samconfig file', async () => {

packages/core/src/test/lambda/vue/remoteInvoke/invokeLambda.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ describe('RemoteInvokeWebview', () => {
194194
async () => await remoteInvokeWebview.promptFile(),
195195
new Error('Failed to read selected file')
196196
)
197-
assertLogsContain('readFileSync: Failed to read file at path %s %O', true, 'error')
197+
assertLogsContain('readFileSync: Failed to read file at path', false, 'error')
198198
})
199199
})
200200

packages/core/src/test/lambda/vue/samInvokeBackend.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ describe('SamInvokeWebview', () => {
443443
async () => await samInvokeWebview.promptFile(),
444444
new Error('Failed to read selected file')
445445
)
446-
assertLogsContain('readFileSync: Failed to read file at path %s %O', true, 'error')
446+
assertLogsContain('readFileSync: Failed to read file at path', false, 'error')
447447
} finally {
448448
await fs.delete(tempFolder, { recursive: true })
449449
}

packages/core/src/test/shared/applicationBuilder/explorer/nodes/deployedNode.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ describe('generateDeployedNode', () => {
214214
lambdaDeployedNodeInput.resourceTreeEntity
215215
)
216216

217-
assertLogsContain('Error getting Lambda configuration %O', true, 'error')
217+
assertLogsContain('Error getting Lambda configuration', false, 'error')
218218
assert(deployedResourceNodes.length === 1)
219219

220220
// Check placeholder propertries

packages/core/src/test/shared/schemas.test.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,32 +42,35 @@ describe('SchemaService', function () {
4242

4343
it('assigns schemas to the yaml extension', async function () {
4444
const stub = sinon.stub()
45+
const fooUri = vscode.Uri.parse('/foo')
46+
const barUri = vscode.Uri.parse('/bar')
4547
fakeYamlExtension.assignSchema = stub
4648
service.registerMapping({
47-
uri: vscode.Uri.parse('/foo'),
49+
uri: fooUri,
4850
type: 'yaml',
4951
schema: 'cfn',
5052
})
5153
service.registerMapping({
52-
uri: vscode.Uri.parse('/bar'),
54+
uri: barUri,
5355
type: 'yaml',
5456
schema: 'sam',
5557
})
5658
await service.processUpdates()
57-
assert(stub.firstCall.calledWithExactly(vscode.Uri.file('/foo'), cfnSchema))
58-
assert(stub.secondCall.calledWithExactly(vscode.Uri.file('/bar'), samSchema))
59+
assert(stub.firstCall.calledWithExactly(fooUri, cfnSchema))
60+
assert(stub.secondCall.calledWithExactly(barUri, samSchema))
5961
})
6062

6163
it('removes schemas from the yaml extension', async function () {
6264
const stub = sinon.stub()
65+
const fooUri = vscode.Uri.parse('/foo')
6366
fakeYamlExtension.removeSchema = stub
6467
service.registerMapping({
65-
uri: vscode.Uri.parse('/foo'),
68+
uri: fooUri,
6669
type: 'yaml',
6770
schema: undefined,
6871
})
6972
await service.processUpdates()
70-
assert(stub.calledOnceWithExactly(vscode.Uri.file('/foo')))
73+
assert(stub.calledOnceWithExactly(fooUri))
7174
})
7275

7376
it('registers schemas to json configuration', async function () {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import assert from 'assert'
7+
import { getLogger } from '../shared'
8+
import { assertLogsContain, getTestLogger } from './globalSetup.test'
9+
10+
describe('TestLogger', function () {
11+
describe('assertLogsContain', function () {
12+
it('checks only at specified log level', function () {
13+
const logger = getLogger()
14+
logger.info('here is some info')
15+
logger.debug('here is some debug')
16+
17+
assertLogsContain('here is some info', true, 'info')
18+
assert.throws(() => assertLogsContain('here is some info', true, 'debug'))
19+
20+
assertLogsContain('here is some debug', true, 'debug')
21+
assert.throws(() => assertLogsContain('here is some debug', true, 'info'))
22+
})
23+
24+
it('only requires substring without exactMatch=true', function () {
25+
const logger = getLogger()
26+
logger.info('here is some info')
27+
logger.debug('here is some debug')
28+
29+
assertLogsContain('some info', false, 'info')
30+
assertLogsContain('some debug', false, 'debug')
31+
})
32+
})
33+
34+
it('formats objects into logs', function () {
35+
const testObj = {
36+
info: 'some info',
37+
}
38+
39+
getLogger().debug('here is my testObj: %O', testObj)
40+
assertLogsContain(`here is my testObj: { info: 'some info' }`, true, 'debug')
41+
})
42+
43+
it('has one logging entry for each logging statement', function () {
44+
const logger = getLogger()
45+
const startingEntries = getTestLogger().getLoggedEntries().length
46+
logger.info('here is some info %O', { info: 'this is info' })
47+
logger.debug('here is some debug %O', { debug: 'this is debug' })
48+
assert.strictEqual(getTestLogger().getLoggedEntries().length - startingEntries, 2)
49+
})
50+
51+
it('returns entry number on each log statement', function () {
52+
const logger = getLogger()
53+
const startingEntryNumber = getTestLogger().getLoggedEntries().length
54+
const entry1 = logger.info('here is some info %O', { info: 'this is info' })
55+
const entry2 = logger.debug('here is some debug %O', { debug: 'this is debug' })
56+
const entry3 = logger.debug('here is some warn %O', { warn: 'this is warn' })
57+
assert.strictEqual(entry1, startingEntryNumber)
58+
assert.strictEqual(entry2, startingEntryNumber + 1)
59+
assert.strictEqual(entry3, startingEntryNumber + 2)
60+
})
61+
})

packages/core/src/test/testLogger.ts

Lines changed: 21 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,64 +3,48 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { Loggable, Logger, LogLevel } from '../shared/logger'
7-
import { compareLogLevel } from '../shared/logger/logger'
6+
import { Loggable, LogLevel } from '../shared/logger'
7+
import { BaseLogger, compareLogLevel } from '../shared/logger/logger'
88
import { Uri } from 'vscode'
9+
import util from 'util'
10+
import { isWeb } from '../shared'
11+
import { inspect } from '../shared/utilities/collectionUtils'
912

1013
/**
1114
* In-memory Logger implementation suitable for use by tests.
1215
*/
13-
export class TestLogger implements Logger {
16+
export class TestLogger extends BaseLogger {
1417
private readonly loggedEntries: {
1518
logLevel: LogLevel
1619
entry: Loggable
1720
}[] = []
1821
private count: number = 0
19-
public constructor(private logLevel: LogLevel = 'debug') {}
20-
logFile?: Uri | undefined
21-
22-
public enableDebugConsole(): void {}
23-
24-
public log(logLevel: LogLevel, ...message: Loggable[]): number {
25-
return this.addLoggedEntries(logLevel, message)
26-
}
27-
28-
public debug(...message: Loggable[]): number {
29-
return this.addLoggedEntries('debug', message)
30-
}
31-
32-
public verbose(...message: Loggable[]): number {
33-
return this.addLoggedEntries('verbose', message)
22+
public constructor(private logLevel: LogLevel = 'debug') {
23+
super()
3424
}
3525

36-
public info(...message: Loggable[]): number {
37-
return this.addLoggedEntries('info', message)
38-
}
39-
40-
public warn(...message: Loggable[]): number {
41-
return this.addLoggedEntries('warn', message)
42-
}
43-
44-
public error(...message: Loggable[]): number {
45-
return this.addLoggedEntries('error', message)
46-
}
26+
public enableDebugConsole(): void {}
4727

4828
public getLoggedEntries(...logLevels: LogLevel[]): Loggable[] {
4929
return this.loggedEntries
5030
.filter((loggedEntry) => logLevels.length === 0 || logLevels.includes(loggedEntry.logLevel))
5131
.map((loggedEntry) => loggedEntry.entry)
5232
}
5333

54-
public sendToLog(logLevel: LogLevel, msg: string, ...entries: Loggable[]): number {
55-
return this.addLoggedEntries(logLevel, [msg, ...entries])
34+
public sendToLog(logLevel: LogLevel, msg: string, ...meta: any[]): number {
35+
return this.addLoggedEntries(logLevel, msg, ...meta)
36+
}
37+
38+
private formatString(message: string, ...meta: any[]): string {
39+
// Want to avoid reimplementing nodes `format` so instead concat to end of string
40+
// Node's format implementation: https://github.com/nodejs/node/blob/3178a762d6a2b1a37b74f02266eea0f3d86603be/lib/internal/util/inspect.js#L2191-L2315
41+
return isWeb() ? [message, meta.map((s) => inspect(s))].join(' ') : util.format(message, ...meta)
5642
}
5743

58-
private addLoggedEntries(logLevel: LogLevel, entries: Loggable[]): number {
59-
entries.forEach((entry) => {
60-
this.loggedEntries.push({
61-
logLevel,
62-
entry,
63-
})
44+
private addLoggedEntries(logLevel: LogLevel, message: Loggable, ...meta: any[]): number {
45+
this.loggedEntries.push({
46+
logLevel,
47+
entry: typeof message === 'string' && meta.length > 0 ? this.formatString(message, ...meta) : message,
6448
})
6549

6650
return this.count++

0 commit comments

Comments
 (0)