Skip to content

Commit

Permalink
Merge e0b34d2 into 147e0b3
Browse files Browse the repository at this point in the history
  • Loading branch information
dubzzz committed Sep 8, 2019
2 parents 147e0b3 + e0b34d2 commit 97d7430
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 13 deletions.
1 change: 1 addition & 0 deletions documentation/1-Guides/Arbitraries.md
Expand Up @@ -69,6 +69,7 @@ More specific strings:
- `fc.unicodeJson()` or `fc.unicodeJson(maxDepth: number)` json strings having keys generated using `fc.unicodeString()`. String values are also produced by `fc.unicodeString()`
- `fc.lorem()`, `fc.lorem(maxWordsCount: number)` or `fc.lorem(maxWordsCount: number, sentencesMode: boolean)` lorem ipsum strings. Generator can be configured by giving it a maximum number of characters by using `maxWordsCount` or switching the mode to sentences by setting `sentencesMode` to `true` in which case `maxWordsCount` is used to cap the number of sentences allowed
- `fc.ipV4()` IP v4 strings
- `fc.ipV4Extended()` IP v4 strings including all the formats supported by WhatWG standard (for instance: 0x6f.9)
- `fc.ipV6()` IP v6 strings
- `fc.domain()` Domain name with extension following RFC 1034, RFC 1123 and WHATWG URL Standard
- `fc.webAuthority()` Web authority following RFC 3986
Expand Down
33 changes: 32 additions & 1 deletion src/check/arbitrary/IpArbitrary.ts
@@ -1,4 +1,5 @@
import { array } from './ArrayArbitrary';
import { constantFrom } from './ConstantArbitrary';
import { Arbitrary } from './definition/Arbitrary';
import { nat } from './IntegerArbitrary';
import { oneof } from './OneOfArbitrary';
Expand All @@ -16,6 +17,36 @@ function ipV4(): Arbitrary<string> {
return tuple(nat(255), nat(255), nat(255), nat(255)).map(([a, b, c, d]) => `${a}.${b}.${c}.${d}`);
}

/**
* For valid IP v4 according to WhatWG
*
* Following WhatWG, the specification for web-browsers
* https://url.spec.whatwg.org/
*
* There is no equivalent for IP v6 according to the IP v6 parser
* https://url.spec.whatwg.org/#concept-ipv6-parser
*/
function ipV4Extended(): Arbitrary<string> {
const natRepr = (maxValue: number) =>
tuple(constantFrom('dec', 'oct', 'hex'), nat(maxValue)).map(([style, v]) => {
switch (style) {
case 'oct':
return `0${Number(v).toString(8)}`;
case 'hex':
return `0x${Number(v).toString(16)}`;
case 'dec':
default:
return `${v}`;
}
});
return oneof(
tuple(natRepr(255), natRepr(255), natRepr(255), natRepr(255)).map(([a, b, c, d]) => `${a}.${b}.${c}.${d}`),
tuple(natRepr(255), natRepr(255), natRepr(65535)).map(([a, b, c]) => `${a}.${b}.${c}`),
tuple(natRepr(255), natRepr(16777215)).map(([a, b]) => `${a}.${b}`),
natRepr(4294967295)
);
}

/**
* For valid IP v6
*
Expand Down Expand Up @@ -55,4 +86,4 @@ function ipV6(): Arbitrary<string> {
);
}

export { ipV4, ipV6 };
export { ipV4, ipV4Extended, ipV6 };
9 changes: 6 additions & 3 deletions src/check/arbitrary/WebArbitrary.ts
@@ -1,10 +1,10 @@
import { array } from './ArrayArbitrary';
import { constant } from './ConstantArbitrary';
import { constantFrom } from './ConstantArbitrary';
import { constant } from './ConstantArbitrary';
import { buildAlphaNumericPercentArb } from './helpers/SpecificCharacterRange';
import { domain, hostUserInfo } from './HostArbitrary';
import { nat } from './IntegerArbitrary';
import { ipV4, ipV6 } from './IpArbitrary';
import { ipV4, ipV4Extended, ipV6 } from './IpArbitrary';
import { oneof } from './OneOfArbitrary';
import { option } from './OptionArbitrary';
import { stringOf } from './StringArbitrary';
Expand All @@ -15,6 +15,8 @@ export interface WebAuthorityConstraints {
withIPv4?: boolean;
/** Enable IPv6 in host */
withIPv6?: boolean;
/** Enable extended IPv4 format */
withIPv4Extended?: boolean;
/** Enable user information prefix */
withUserInfo?: boolean;
/** Enable port suffix */
Expand All @@ -32,7 +34,8 @@ export function webAuthority(constraints?: WebAuthorityConstraints) {
const c = constraints || {};
const hostnameArbs = [domain()]
.concat(c.withIPv4 === true ? [ipV4()] : [])
.concat(c.withIPv6 === true ? [ipV6().map(ip => `[${ip}]`)] : []);
.concat(c.withIPv6 === true ? [ipV6().map(ip => `[${ip}]`)] : [])
.concat(c.withIPv4Extended === true ? [ipV4Extended()] : []);
return tuple(
c.withUserInfo === true ? option(hostUserInfo()) : constant(null),
oneof(...hostnameArbs),
Expand Down
3 changes: 2 additions & 1 deletion src/fast-check-default.ts
Expand Up @@ -23,7 +23,7 @@ import { frequency } from './check/arbitrary/FrequencyArbitrary';
import { compareBooleanFunc, compareFunc, func } from './check/arbitrary/FunctionArbitrary';
import { domain } from './check/arbitrary/HostArbitrary';
import { integer, maxSafeInteger, maxSafeNat, nat } from './check/arbitrary/IntegerArbitrary';
import { ipV4, ipV6 } from './check/arbitrary/IpArbitrary';
import { ipV4, ipV4Extended, ipV6 } from './check/arbitrary/IpArbitrary';
import { letrec } from './check/arbitrary/LetRecArbitrary';
import { lorem } from './check/arbitrary/LoremArbitrary';
import { mapToConstant } from './check/arbitrary/MapToConstantArbitrary';
Expand Down Expand Up @@ -160,6 +160,7 @@ export {
date,
// web
ipV4,
ipV4Extended,
ipV6,
domain,
webAuthority,
Expand Down
3 changes: 3 additions & 0 deletions test/e2e/NoRegression.spec.ts
Expand Up @@ -194,6 +194,9 @@ describe(`NoRegression`, () => {
it('ipV4', () => {
expect(() => fc.assert(fc.property(fc.ipV4(), v => testFunc(v)), settings)).toThrowErrorMatchingSnapshot();
});
it('ipV4Extended', () => {
expect(() => fc.assert(fc.property(fc.ipV4Extended(), v => testFunc(v)), settings)).toThrowErrorMatchingSnapshot();
});
it('ipV6', () => {
expect(() => fc.assert(fc.property(fc.ipV6(), v => testFunc(v)), settings)).toThrowErrorMatchingSnapshot();
});
Expand Down
16 changes: 16 additions & 0 deletions test/e2e/__snapshots__/NoRegression.spec.ts.snap
Expand Up @@ -1352,6 +1352,22 @@ Execution summary:
. . . . . . . √ [\\"0.54.0.0\\"]"
`;
exports[`NoRegression ipV4Extended 1`] = `
"Property failed after 1 tests
{ seed: 42, path: \\"0:1:1\\", endOnFailure: true }
Counterexample: [\\"00.0\\"]
Shrunk 2 time(s)
Got error: Property failed by returning false
Execution summary:
× [\\"0112.0x0\\"]
. √ [\\"74.0x0\\"]
. × [\\"00.0x0\\"]
. . √ [\\"0.0x0\\"]
. . × [\\"00.0\\"]
. . . √ [\\"0.0\\"]"
`;
exports[`NoRegression ipV6 1`] = `
"Property failed after 1 tests
{ seed: 42, path: \\"0:0:0:0:0:0\\", endOnFailure: true }
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/arbitraries/WebArbitrary.spec.ts
Expand Up @@ -35,7 +35,7 @@ describe(`WebArbitrary (seed: ${seed})`, () => {
it('Should produce valid URL parts', () => {
fc.assert(
fc.property(
fc.webAuthority({ withIPv4: true, withIPv6: true, withUserInfo: true, withPort: true }),
fc.webAuthority({ withIPv4: true, withIPv6: true, withIPv4Extended: true, withUserInfo: true, withPort: true }),
fc.array(fc.webSegment()).map(p => p.map(v => `/${v}`).join('')),
fc.webQueryParameters(),
fc.webFragments(),
Expand Down
35 changes: 28 additions & 7 deletions test/unit/check/arbitrary/IpArbitrary.spec.ts
@@ -1,15 +1,28 @@
import { ipV4, ipV6 } from '../../../../src/check/arbitrary/IpArbitrary';
import { ipV4, ipV6, ipV4Extended } from '../../../../src/check/arbitrary/IpArbitrary';

import * as genericHelper from './generic/GenericArbitraryHelper';

const isValidIpV4 = (i: string) => {
if (typeof i !== 'string') return false;
const m = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(i);
if (m === null) return false;
return [m[1], m[2], m[3], m[4]].every(g => {
const n = +g;
return n >= 0 && n <= 255 && String(n) === g;
const chunks = i.split('.').map(v => {
if (v[0] === '0') {
if (v[1] === 'x' || v[1] === 'X') return parseInt(v, 16);
return parseInt(v, 8);
}
return parseInt(v, 10);
});

// one invalid chunk
if (chunks.find(v => Number.isNaN(v)) !== undefined) return false;

// maximal amount of 4 chunks
if (chunks.length > 4) return false;

// all chunks, except the last one are inferior or equal to 255
if (chunks.slice(0, -1).find(v => v < 0 && v > 255) !== undefined) return false;

// last chunk must be below 256^(5 − number of chunks)
return chunks[chunks.length - 1] < 256 ** (5 - chunks.length);
};
const isValidIpV6 = (i: string) => {
if (typeof i !== 'string') return false;
Expand All @@ -19,7 +32,10 @@ const isValidIpV6 = (i: string) => {
if (i.substr(firstElision + 1).includes('::')) return false;
}
const chunks = i.split(':');
const endByIpV4 = isValidIpV4(chunks[chunks.length - 1]);
const last = chunks[chunks.length - 1];
// The ipv4 can only be composed of 4 decimal chunks separated by dots
// 1.1000 is not a valid IP v4 in the context of IP v6
const endByIpV4 = last.includes('.') && isValidIpV4(last);

const nonEmptyChunks = chunks.filter(c => c !== '');
const hexaChunks = endByIpV4 ? nonEmptyChunks.slice(0, nonEmptyChunks.length - 1) : nonEmptyChunks;
Expand All @@ -35,6 +51,11 @@ describe('IpArbitrary', () => {
isValidValue: (g: string) => isValidIpV4(g)
});
});
describe('ipV4Extended', () => {
genericHelper.isValidArbitrary(() => ipV4Extended(), {
isValidValue: (g: string) => isValidIpV4(g)
});
});
describe('ipV6', () => {
genericHelper.isValidArbitrary(() => ipV6(), {
isValidValue: (g: string) => isValidIpV6(g)
Expand Down
1 change: 1 addition & 0 deletions test/unit/check/arbitrary/WebArbitrary.spec.ts
Expand Up @@ -28,6 +28,7 @@ describe('WebArbitrary', () => {
{
withIPv4: fc.boolean(),
withIPv6: fc.boolean(),
withIPv4Extended: fc.boolean(),
withUserInfo: fc.boolean(),
withPort: fc.boolean()
},
Expand Down

0 comments on commit 97d7430

Please sign in to comment.