-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Use FNV instead of SHA512 for the hashing in A/B test #7260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
83036aa
5b3fa08
577750c
426991f
9d3cbc5
8806db9
3a7cd22
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Fixed A/B testing sampling |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,11 +3,18 @@ | |
|
|
||
| 'use strict'; | ||
|
|
||
| import { assert } from 'chai'; | ||
| import { assert, expect } from 'chai'; | ||
| import * as path from 'path'; | ||
| import { CryptoUtils } from '../../client/common/crypto'; | ||
| import { FileSystem } from '../../client/common/platform/fileSystem'; | ||
| import { PlatformService } from '../../client/common/platform/platformService'; | ||
| import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../constants'; | ||
|
|
||
| // tslint:disable-next-line: max-func-body-length | ||
| suite('Crypto Utils', async () => { | ||
| let crypto: CryptoUtils; | ||
| const fs = new FileSystem(new PlatformService()); | ||
| const file = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src', 'test', 'common', 'randomWords.txt'); | ||
| setup(() => { | ||
| crypto = new CryptoUtils(); | ||
| }); | ||
|
|
@@ -45,4 +52,78 @@ suite('Crypto Utils', async () => { | |
| const hash2 = crypto.createHash('hash2', 'string'); | ||
| assert.notEqual(hash1, hash2, 'Hashes should be different strings'); | ||
| }); | ||
| test('If hashFormat equals `number`, ensure numbers are uniformly distributed on scale from 0 to 100', async () => { | ||
| const words = await fs.readFile(file); | ||
| const wordList = words.split('\n'); | ||
| const buckets: number[] = Array(100).fill(0); | ||
| const hashes = Array(10).fill(0); | ||
| for (const w of wordList) { | ||
| for (let i = 0; i < 10; i += 1) { | ||
| const word = `${w}${i}`; | ||
| const hash = crypto.createHash(word, 'number'); | ||
| buckets[hash % 100] += 1; | ||
| hashes[i] = hash % 100; | ||
| } | ||
| } | ||
| // Total number of words = wordList.length * 10, because we added ten variants of each word above. | ||
| const expectedHitsPerBucket = wordList.length * 10 / 100; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like some of the items in
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually we don't need to ignore anything, so removing that line. |
||
| for (const hit of buckets) { | ||
| expect(hit).to.be.lessThan(1.25 * expectedHitsPerBucket); | ||
| expect(hit).to.be.greaterThan(0.75 * expectedHitsPerBucket); | ||
| } | ||
| }); | ||
| test('If hashFormat equals `number`, on a scale of 0 to 100, small difference in the input on average produce large differences (about 33) in the output ', async () => { | ||
| const words = await fs.readFile(file); | ||
| const wordList = words.split('\n'); | ||
| const buckets: number[] = Array(100).fill(0); | ||
| let hashes: number[] = []; | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let totalDifference = 0; | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // We are only iterating over the first 10 words for purposes of this test | ||
| for (const w of wordList.slice(0, 10)) { | ||
| hashes = []; | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| totalDifference = 0; | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (w.length === 0) { | ||
| continue; | ||
| } | ||
| for (let i = 0; i < 10; i += 1) { | ||
| const word = `${w}${i}`; | ||
| const hash = crypto.createHash(word, 'number'); | ||
| buckets[hash % 100] += 1; | ||
| hashes.push(hash % 100); | ||
| } | ||
| for (let i = 0; i < 10; i += 1) { | ||
| const word = `${i}${w}`; | ||
| const hash = crypto.createHash(word, 'number'); | ||
| buckets[hash % 100] += 1; | ||
| hashes.push(hash % 100); | ||
| } | ||
| // Iterating over ASCII alphabets 'a' to 'z' and appending to the word | ||
| for (let i = 0; i < 26; i += 1) { | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const word = `${String.fromCharCode(97 + i)}${w}`; | ||
| const hash = crypto.createHash(word, 'number'); | ||
| buckets[hash % 100] += 1; | ||
| hashes.push(hash % 100); | ||
| } | ||
| // Iterating over ASCII alphabets 'a' to 'z' and prepending to the word | ||
| for (let i = 0; i < 26; i += 1) { | ||
| const word = `${w}${String.fromCharCode(97 + i)}`; | ||
| const hash = crypto.createHash(word, 'number'); | ||
| buckets[hash % 100] += 1; | ||
| hashes.push(hash % 100); | ||
| } | ||
| // tslint:disable: prefer-for-of | ||
| for (let i = 0; i < hashes.length; i += 1) { | ||
| for (let j = 0; j < hashes.length; j += 1) { | ||
| if (hashes[i] > hashes[j]) { | ||
| totalDifference += hashes[i] - hashes[j]; | ||
| } else { | ||
| totalDifference += hashes[j] - hashes[i]; | ||
| } | ||
| } | ||
| } | ||
| const averageDifference = totalDifference / hashes.length / hashes.length; | ||
| expect(averageDifference).to.be.lessThan(1.25 * 33); | ||
| expect(averageDifference).to.be.greaterThan(0.75 * 33); | ||
| } | ||
| }); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.