-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: concurrency tests in configFile
- Loading branch information
Showing
5 changed files
with
252 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
/* | ||
* Copyright (c) 2023, salesforce.com, inc. | ||
* All rights reserved. | ||
* Licensed under the BSD 3-Clause license. | ||
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause | ||
*/ | ||
import { join } from 'node:path'; | ||
import { tmpdir } from 'node:os'; | ||
import { rm } from 'node:fs/promises'; | ||
import { expect } from 'chai'; | ||
import { sleep } from '@salesforce/kit'; | ||
import { ConfigFile } from '../../../src/exported'; | ||
|
||
const FILENAME = 'concurrency.json'; | ||
const sharedLocation = join('sfdx-core-ut', 'test', 'configFile'); | ||
|
||
class TestConfig extends ConfigFile<ConfigFile.Options> { | ||
public static getOptions( | ||
filename: string, | ||
isGlobal: boolean, | ||
isState?: boolean, | ||
filePath?: string | ||
): ConfigFile.Options { | ||
return { | ||
rootFolder: tmpdir(), | ||
filename, | ||
isGlobal, | ||
isState, | ||
filePath, | ||
}; | ||
} | ||
|
||
public static getFileName() { | ||
return FILENAME; | ||
} | ||
} | ||
|
||
/* file and node - clock timestamps aren't precise enough to run in a UT. | ||
* the goal of this and the `sleep` is to put a bit of space between operations | ||
* to simulate real-world concurrency where it's unlikely to hit the same ms | ||
*/ | ||
const SLEEP_FUDGE_MS = 5; | ||
|
||
describe('concurrency', () => { | ||
beforeEach(async () => { | ||
await rm(join(tmpdir(), '.sfdx', 'sfdx-core-ut'), { recursive: true, force: true }); | ||
}); | ||
afterEach(async () => { | ||
await rm(join(tmpdir(), '.sfdx', 'sfdx-core-ut'), { recursive: true, force: true }); | ||
}); | ||
|
||
it('merges in new props from file saved since a prop was set in memory', async () => { | ||
const config1 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
config1.set('x', 0); | ||
await sleep(SLEEP_FUDGE_MS); | ||
const config2 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
config2.set('x', 1); | ||
await config2.write(); | ||
|
||
const c1output = await config1.write(); | ||
expect(c1output.x).to.equal(1); | ||
}); | ||
|
||
it('newer props beat older files', async () => { | ||
const config1 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
|
||
const config2 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
config2.set('x', 1); | ||
await config2.write(); | ||
await sleep(SLEEP_FUDGE_MS); | ||
|
||
config1.set('x', 0); | ||
await sleep(SLEEP_FUDGE_MS); | ||
|
||
const c1output = await config1.write(); | ||
expect(c1output.x).to.equal(0); | ||
}); | ||
|
||
it('"deleted" (missing) props in a file do not delete from contents"', async () => { | ||
const config1 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
config1.set('x', 0); | ||
await sleep(SLEEP_FUDGE_MS); | ||
|
||
const config2 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
config2.set('x', 1); | ||
config2.unset('x'); | ||
await sleep(SLEEP_FUDGE_MS); | ||
|
||
await config2.write(); | ||
await sleep(SLEEP_FUDGE_MS); | ||
|
||
const c1output = await config1.write(); | ||
expect(c1output.x).to.equal(0); | ||
}); | ||
|
||
it('newer deleted props beat older files', async () => { | ||
const config1 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
config1.set('x', 0); | ||
await sleep(SLEEP_FUDGE_MS); | ||
|
||
const config2 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
config2.set('x', 1); | ||
|
||
await config2.write(); | ||
await sleep(SLEEP_FUDGE_MS); | ||
|
||
config1.unset('x'); | ||
|
||
const c1output = await config1.write(); | ||
expect(c1output.x).to.equal(undefined); | ||
}); | ||
|
||
it('deleted in both memory and file', async () => { | ||
const config1 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
config1.set('x', 0); | ||
await sleep(SLEEP_FUDGE_MS); | ||
|
||
const config2 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
config2.set('x', 1); | ||
|
||
await config2.write(); | ||
await sleep(SLEEP_FUDGE_MS); | ||
|
||
config2.unset('x'); | ||
await config2.write(); | ||
|
||
await sleep(SLEEP_FUDGE_MS); | ||
config1.unset('x'); | ||
|
||
const c1output = await config1.write(); | ||
expect(c1output.x).to.equal(undefined); | ||
}); | ||
|
||
it('parallel writes from different processes produce valid json when file exists', async () => { | ||
const config1 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
await config1.write(); | ||
const config2 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
const config3 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
const config4 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); | ||
|
||
config1.set('x', 0); | ||
await sleep(SLEEP_FUDGE_MS); | ||
config2.set('x', 1); | ||
config2.set('b', 2); | ||
await sleep(SLEEP_FUDGE_MS); | ||
config3.set('x', 2); | ||
config3.set('c', 3); | ||
await sleep(SLEEP_FUDGE_MS); | ||
config4.set('x', 3); | ||
await sleep(SLEEP_FUDGE_MS); | ||
|
||
// simulate non-deterministic parallel operations from different processes. We can't guarantee the order of files, | ||
// so this might result in a set prop being deleted. | ||
await Promise.all([config1.write(), config2.write(), config3.write(), config4.write()]); | ||
await Promise.all([config1.read(), config2.read(), config3.read(), config4.read()]); | ||
// timestamps on the files are treated as the stamp for all props, | ||
// since we lose the CRDT prop - level timestamps when writing to json | ||
expect(config1.get('x')).to.be.greaterThanOrEqual(0).and.lessThanOrEqual(3); | ||
if (config1.has('b')) { | ||
expect(config1.get('b')).to.equal(2); | ||
} | ||
if (config1.has('c')) { | ||
expect(config1.get('c')).to.equal(3); | ||
} | ||
if (config2.has('b')) { | ||
expect(config2.get('b')).to.equal(2); | ||
} | ||
if (config2.has('c')) { | ||
expect(config2.get('c')).to.equal(3); | ||
} | ||
}); | ||
|
||
it('parallel writes, reverse order, with deletes', async () => { | ||
const config1 = new TestConfig(TestConfig.getOptions('test', true, true, sharedLocation)); | ||
const config2 = new TestConfig(TestConfig.getOptions('test', true, true, sharedLocation)); | ||
const config3 = new TestConfig(TestConfig.getOptions('test', true, true, sharedLocation)); | ||
const config4 = new TestConfig(TestConfig.getOptions('test', true, true, sharedLocation)); | ||
|
||
config1.set('x', 7); | ||
await sleep(SLEEP_FUDGE_MS); | ||
config2.set('x', 8); | ||
await sleep(SLEEP_FUDGE_MS); | ||
config3.set('x', 9); | ||
await sleep(SLEEP_FUDGE_MS); | ||
config4.unset('x'); | ||
|
||
await Promise.all([config4.write(), config3.write(), config2.write(), config1.write()]); | ||
await Promise.all([config1.read(), config2.read(), config3.read(), config4.read()]); | ||
if (config4.has('x')) { | ||
expect(config4.get('x')).to.be.greaterThanOrEqual(7).and.lessThanOrEqual(9); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ead7330
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logger Benchmarks - ubuntu-latest
Child logger creation
432600
ops/sec (±9.99%
)473126
ops/sec (±8.65%
)1.09
Logging a string on root logger
524492
ops/sec (±10.11%
)637687
ops/sec (±11.36%
)1.22
Logging an object on root logger
324483
ops/sec (±17.10%
)378667
ops/sec (±16.24%
)1.17
Logging an object with a message on root logger
281653
ops/sec (±11.80%
)214855
ops/sec (±18.32%
)0.76
Logging an object with a redacted prop on root logger
274962
ops/sec (±15.67%
)258952
ops/sec (±17.47%
)0.94
Logging a nested 3-level object on root logger
2222
ops/sec (±220.42%
)11460
ops/sec (±187.02%
)5.16
This comment was automatically generated by workflow using github-action-benchmark.
ead7330
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible performance regression was detected for benchmark 'Logger Benchmarks - ubuntu-latest'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold
2
.Logging a nested 3-level object on root logger
2222
ops/sec (±220.42%
)11460
ops/sec (±187.02%
)5.16
This comment was automatically generated by workflow using github-action-benchmark.
ead7330
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logger Benchmarks - windows-latest
Child logger creation
342127
ops/sec (±7.26%
)391874
ops/sec (±1.33%
)1.15
Logging a string on root logger
441560
ops/sec (±14.38%
)461299
ops/sec (±10.27%
)1.04
Logging an object on root logger
256861
ops/sec (±26.20%
)285973
ops/sec (±20.41%
)1.11
Logging an object with a message on root logger
168638
ops/sec (±17.86%
)167689
ops/sec (±17.87%
)0.99
Logging an object with a redacted prop on root logger
177038
ops/sec (±24.65%
)165913
ops/sec (±21.87%
)0.94
Logging a nested 3-level object on root logger
120811
ops/sec (±23.64%
)117310
ops/sec (±24.53%
)0.97
This comment was automatically generated by workflow using github-action-benchmark.