Skip to content
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

Support all the range of valid ipV4 #393

Merged
merged 3 commits into from
Sep 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions documentation/1-Guides/Arbitraries.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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