Skip to content

Commit

Permalink
test, feat: link minimum version to tests using pipes
Browse files Browse the repository at this point in the history
  • Loading branch information
EagleoutIce committed Aug 26, 2023
1 parent 127bb1e commit 5ea6756
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 9 deletions.
7 changes: 7 additions & 0 deletions src/r-bridge/lang-4.x/ast/model/versions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Link certain concepts to the version they have been introduced in R
*
* @module
*/

export const MIN_VERSION_PIPE = "4.1.0"
1 change: 1 addition & 0 deletions src/r-bridge/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export class RShell {
this._sendCommand(command)
}

// TODO: cache?
public async usedRVersion(): Promise<SemVer | null> {
// retrieve raw version:
const result = await this.sendCommandWithOutput(`cat(paste0(R.version$major,".",R.version$minor), ${ts2r(this.options.eol)})`)
Expand Down
10 changes: 7 additions & 3 deletions test/dataflow/elements/atomic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { RAssignmentOpPool, RNonAssignmentBinaryOpPool, RUnaryOpPool } from '../
import { appendEnvironments, define } from '../../../src/dataflow/environments'
import { UnnamedArgumentPrefix } from '../../../src/dataflow/internal/process/functions/argument'
import { GlobalScope, LocalScope } from '../../../src/dataflow/environments/scopes'
import { MIN_VERSION_PIPE } from '../../../src/r-bridge/lang-4.x/ast/model/versions'

describe("Atomic dataflow information", withShell((shell) => {
describe("uninteresting leafs", () => {
Expand Down Expand Up @@ -138,7 +139,8 @@ describe("Atomic dataflow information", withShell((shell) => {
})
.addVertex({ tag: 'use', id: "1", name: `${UnnamedArgumentPrefix}1` })
.addEdge("3", "1", EdgeType.Argument, "always")
.addEdge("1", "0", EdgeType.Reads, "always")
.addEdge("1", "0", EdgeType.Reads, "always"),
{ minRVersion: MIN_VERSION_PIPE }
)
assertDataflow("Nested calling", shell, "x |> f() |> g()",
new DataflowGraph()
Expand All @@ -160,7 +162,8 @@ describe("Atomic dataflow information", withShell((shell) => {
.addEdge("3", "1", EdgeType.Argument, "always")
.addEdge("7", "5", EdgeType.Argument, "always")
.addEdge("5", "3", EdgeType.Reads, "always")
.addEdge("1", "0", EdgeType.Reads, "always")
.addEdge("1", "0", EdgeType.Reads, "always"),
{ minRVersion: MIN_VERSION_PIPE }
)
assertDataflow("Multi-Parameter function", shell, "x |> f(y,z)",
new DataflowGraph()
Expand All @@ -186,7 +189,8 @@ describe("Atomic dataflow information", withShell((shell) => {
.addEdge("7", "6", EdgeType.Argument, "always")
.addEdge("1", "0", EdgeType.Reads, "always")
.addEdge("4", "3", EdgeType.Reads, "always")
.addEdge("6", "5", EdgeType.Reads, "always")
.addEdge("6", "5", EdgeType.Reads, "always"),
{ minRVersion: MIN_VERSION_PIPE }
)
})
})
Expand Down
36 changes: 32 additions & 4 deletions test/helper/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { assert } from 'chai'
import { DataflowGraph, diffGraphsToMermaidUrl, graphToMermaidUrl, produceDataFlowGraph } from '../../src/dataflow'
import { reconstructToCode, SlicingCriteria, slicingCriterionToId, staticSlicing } from '../../src/slicing'
import { LocalScope } from '../../src/dataflow/environments/scopes'
import { testRequiresRVersion } from './version'
import { deepMergeObject, MergeableRecord } from '../../src/util/objects'

let defaultTokenMap: Record<string, string>

Expand Down Expand Up @@ -102,26 +104,50 @@ export const retrieveAst = async(shell: RShell, input: `file://${string}` | stri
}, defaultTokenMap, shell, hooks)
}

export interface TestConfiguration extends MergeableRecord {
minRVersion: string | undefined,
needsNetworkConnection: boolean,
}

export const defaultTestConfiguration: TestConfiguration = {
minRVersion: undefined,
needsNetworkConnection: false,
}

async function ensureConfig(shell: RShell, test: Mocha.Context, userConfig?: Partial<TestConfiguration>): Promise<void> {
const config = deepMergeObject(defaultTestConfiguration, userConfig)
if(config.needsNetworkConnection) {
await testRequiresNetworkConnection(test)
}
if(config.minRVersion !== undefined) {
await testRequiresRVersion(shell, config.minRVersion, test)
}
}

/** call within describeSession */
export const assertAst = (name: string, shell: RShell, input: string, expected: RExpressionList): Mocha.Test => {
export const assertAst = (name: string, shell: RShell, input: string, expected: RExpressionList, userConfig?: Partial<TestConfiguration>): Mocha.Test => {
// the ternary operator is to support the legacy way I wrote these tests - by mirroring the input within the name
return it(name === input ? name : `${name} (input: ${input})`, async function() {
await ensureConfig(shell, this, userConfig)
const ast = await retrieveAst(shell, input)
assertAstEqualIgnoreSourceInformation(ast, expected, `got: ${JSON.stringify(ast)}, vs. expected: ${JSON.stringify(expected)}`)
})
}

/** call within describeSession */
export function assertDecoratedAst<Decorated>(name: string, shell: RShell, input: string, decorator: (input: RNode) => RNode<Decorated>, expected: RNodeWithParent<Decorated>): void {
export function assertDecoratedAst<Decorated>(name: string, shell: RShell, input: string, decorator: (input: RNode) => RNode<Decorated>, expected: RNodeWithParent<Decorated>, userConfig?: Partial<TestConfiguration>): void {
it(name, async function() {
await ensureConfig(shell, this, userConfig)
const baseAst = await retrieveAst(shell, input)
const ast = decorator(baseAst)
assertAstEqualIgnoreSourceInformation(ast, expected, `got: ${JSON.stringify(ast)}, vs. expected: ${JSON.stringify(expected)} (baseAst before decoration: ${JSON.stringify(baseAst)})`)
})
}

export const assertDataflow = (name: string, shell: RShell, input: string, expected: DataflowGraph, startIndexForDeterministicIds = 0): void => {
export const assertDataflow = (name: string, shell: RShell, input: string, expected: DataflowGraph, userConfig?: Partial<TestConfiguration>, startIndexForDeterministicIds = 0): void => {
it(`${name} (input: ${JSON.stringify(input)})`, async function() {
await ensureConfig(shell, this, userConfig)

const ast = await retrieveAst(shell, input)
const decoratedAst = decorateAst(ast, deterministicCountingIdGenerator(startIndexForDeterministicIds))

Expand All @@ -143,9 +169,11 @@ export const assertDataflow = (name: string, shell: RShell, input: string, expec
function printIdMapping(ids: NodeId[], map: DecoratedAstMap): string {
return ids.map(id => `${id}: ${JSON.stringify(map.get(id)?.lexeme)}`).join(', ')
}
export const assertReconstructed = (name: string, shell: RShell, input: string, ids: NodeId | NodeId[], expected: string, getId: IdGenerator<NoInfo> = deterministicCountingIdGenerator(0)): Mocha.Test => {
export const assertReconstructed = (name: string, shell: RShell, input: string, ids: NodeId | NodeId[], expected: string, userConfig?: Partial<TestConfiguration>, getId: IdGenerator<NoInfo> = deterministicCountingIdGenerator(0)): Mocha.Test => {
const selectedIds = Array.isArray(ids) ? ids : [ids]
return it(name, async function() {
await ensureConfig(shell, this, userConfig)

const ast = await retrieveAst(shell, input)
const decoratedAst = decorateAst(ast, getId)
const reconstructed = reconstructToCode<NoInfo>(decoratedAst, new Set(selectedIds))
Expand Down
22 changes: 22 additions & 0 deletions test/helper/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { RShell } from '../../src/r-bridge'
import semver from 'semver/preload'
import { MIN_VERSION_PIPE } from '../../src/r-bridge/lang-4.x/ast/model/versions'

/**
* Automatically skip a test if it does not satisfy the given version pattern (for a [semver](https://www.npmjs.com/package/semver) version).
*
* @param shell - The shell to use to query for the version it uses
* @param versionToSatisfy - The version pattern to satisfy (e.g., `"<= 4.0.0 || 5.0.0 - 6.0.0"`)
* @param test - The test to skip if the version does not satisfy the pattern
*/
export const testRequiresRVersion = async(shell: RShell, versionToSatisfy: string, test: Mocha.Context): Promise<void> => {
const version = await shell.usedRVersion()
if (version === null || !semver.satisfies(version, versionToSatisfy)) {
console.warn(`Skipping test because ${JSON.stringify(version)} does not satisfy ${JSON.stringify(versionToSatisfy)}.`)
test.skip()
}
}

export const testRequiresPipeOperator = async(shell: RShell, test: Mocha.Context): Promise<void> => {
await testRequiresRVersion(shell, `>= ${MIN_VERSION_PIPE}`, test)
}
7 changes: 5 additions & 2 deletions test/r-bridge/lang/ast/parse-pipes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { assertAst, withShell } from "../../../helper/shell"
import { exprList } from '../../../helper/ast-builder'
import { rangeFrom } from '../../../../src/util/range'
import { Type } from '../../../../src/r-bridge'
import { MIN_VERSION_PIPE } from '../../../../src/r-bridge/lang-4.x/ast/model/versions'

describe("Parse Pipes", withShell(shell => {
assertAst(
Expand Down Expand Up @@ -44,7 +45,8 @@ describe("Parse Pipes", withShell(shell => {
info: {},
}
}
})
}),
{ minRVersion: MIN_VERSION_PIPE }
)
assertAst(
"x |> f() |> g()",
Expand Down Expand Up @@ -115,7 +117,8 @@ describe("Parse Pipes", withShell(shell => {
info: {}
}
}
})
}),
{ minRVersion: MIN_VERSION_PIPE }
)
}))

0 comments on commit 5ea6756

Please sign in to comment.