-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add functions and unit tests.
- Loading branch information
0 parents
commit 80abcf1
Showing
3 changed files
with
229 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
/** | ||
* @file Unit tests for code contract utility | ||
*/ | ||
import { | ||
assertEquals, | ||
assertThrows, | ||
} from "https://deno.land/std/testing/asserts.ts"; | ||
|
||
import { | ||
codeContract, | ||
disableContract, | ||
enableContract, | ||
} from "./code-contract.ts"; | ||
|
||
// | ||
// Variables | ||
// | ||
|
||
let sideEffectTarget = true; | ||
|
||
// | ||
// Functions | ||
// | ||
|
||
const func = (lhs: number, rhs: number) => (lhs + rhs); | ||
|
||
// | ||
// Test cases | ||
// | ||
|
||
Deno.test({ | ||
name: "It should success the function call if the contract is kept", | ||
fn: () => { | ||
// arrange | ||
enableContract(); | ||
sideEffectTarget = false; | ||
const contracted = codeContract(func, { | ||
pre: (lhs: number, rhs: number) => 0 < lhs && 0 < rhs, | ||
post: (result: number) => (result === 30), | ||
}); | ||
|
||
// act & assert | ||
assertEquals(contracted(10, 20), 30); | ||
}, | ||
}); | ||
Deno.test({ | ||
name: "It should fail the function call if the pre condition is invalid", | ||
fn: () => { | ||
assertThrows( | ||
() => { | ||
// arrange | ||
enableContract(); | ||
sideEffectTarget = false; | ||
const contracted = codeContract(func, { | ||
pre: (lhs: number, rhs: number) => 0 < lhs && 0 < rhs, | ||
}); | ||
|
||
// act & assert | ||
assertEquals(contracted(20, -10), 10); | ||
}, | ||
); | ||
}, | ||
}); | ||
Deno.test({ | ||
name: "It should fail the function call if the post condition is invalid", | ||
fn: () => { | ||
assertThrows( | ||
() => { | ||
// arrange | ||
enableContract(); | ||
sideEffectTarget = false; | ||
const contracted = codeContract(func, { | ||
post: (result: number) => (result === 30), | ||
}); | ||
|
||
// act & assert | ||
assertEquals(contracted(20, 20), 40); | ||
}, | ||
); | ||
}, | ||
}); | ||
Deno.test({ | ||
name: | ||
"It should fail the function call if the invariant condition is invalid", | ||
fn: () => { | ||
assertThrows( | ||
() => { | ||
// arrange | ||
enableContract(); | ||
sideEffectTarget = false; | ||
|
||
const funcWithSideEffect = ( | ||
lhs: number, | ||
rhs: number, | ||
) => (sideEffectTarget = true, lhs + rhs); | ||
const contracted = codeContract(funcWithSideEffect, { | ||
invariant: () => (sideEffectTarget === false), | ||
}); | ||
|
||
// act & assert | ||
assertEquals(contracted(20, 20), 40); | ||
}, | ||
); | ||
}, | ||
}); | ||
Deno.test({ | ||
name: "It should omit contract check if contract check is disabled", | ||
fn: () => { | ||
// arrange | ||
disableContract(); | ||
sideEffectTarget = false; | ||
const contracted = codeContract(func, { | ||
pre: (lhs: number, rhs: number) => 0 < lhs && 0 < rhs, | ||
post: (result: number) => (result === 30), | ||
}); | ||
|
||
// act & assert | ||
assertEquals(contracted(-10, -20), -30); | ||
|
||
enableContract(); | ||
}, | ||
}); |
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,102 @@ | ||
/** | ||
* @file Code contract utility | ||
*/ | ||
// | ||
// Types | ||
// | ||
|
||
// deno-lint-ignore no-explicit-any | ||
type FunctionType<T> = ((...args: any[]) => T) & { name: string }; | ||
type ContractType<TResult> = { | ||
// deno-lint-ignore no-explicit-any | ||
pre?: (...args: any[]) => boolean; | ||
post?: (result: TResult) => boolean; | ||
invariant?: () => boolean; | ||
}; | ||
|
||
// | ||
// Variables | ||
// | ||
|
||
let checkContract = true; | ||
|
||
// | ||
// Functions | ||
// | ||
|
||
/** | ||
* Bind function with code contract | ||
* @param fn Function to bind | ||
* @param contract Code contract | ||
* @returns Bound function | ||
*/ | ||
export function codeContract< | ||
T extends FunctionType<ReturnType<T>>, | ||
>( | ||
fn: T, | ||
contract: ContractType<ReturnType<T>> = {}, | ||
): T { | ||
if (checkContract === false) { | ||
return fn; | ||
} | ||
return ((...args) => { | ||
const check = requireContractCheck(); | ||
if ( | ||
check !== false && contract.pre !== undefined && | ||
contract.pre(...args) === false | ||
) { | ||
const msg = [ | ||
"Code Contract: Failed to assert the pre condition.", | ||
`\tfunction: ${fn.name}`, | ||
`\targs: ${JSON.stringify(args)}`, | ||
]; | ||
throw new Error(msg.join("\n")); | ||
} | ||
const result = fn(...args); | ||
if ( | ||
check !== false && contract.post !== undefined && | ||
contract.post(result) === false | ||
) { | ||
const msg = [ | ||
"Code Contract: Failed to assert the post condition.", | ||
`\tfunction: ${fn.name}`, | ||
`\tresult: ${JSON.stringify(args)}`, | ||
]; | ||
throw new Error(msg.join("\n")); | ||
} | ||
if ( | ||
check !== false && contract.invariant !== undefined && | ||
contract.invariant() === false | ||
) { | ||
const msg = [ | ||
"Code Contract: Failed to assert the invariant.", | ||
`\tfunction: ${fn.name}`, | ||
]; | ||
throw new Error(msg.join("\n")); | ||
} | ||
|
||
return result; | ||
}) as T; | ||
} | ||
|
||
/** | ||
* Enable contract | ||
*/ | ||
export function enableContract() { | ||
checkContract = true; | ||
} | ||
|
||
/** | ||
* Disable contract check | ||
*/ | ||
export function disableContract() { | ||
checkContract = false; | ||
} | ||
|
||
/** | ||
* Require contract check | ||
* @returns Require contract check flag | ||
*/ | ||
function requireContractCheck() { | ||
return checkContract; | ||
} |
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,5 @@ | ||
{ | ||
"compilerOptions": { | ||
"strict": true | ||
} | ||
} |