gui3
gui3

Reputation: 1877

Evaluate a "custom test" file in a safe scope with access only to chosen local variables

Context

I am writing a "testing" utility for a specific use case.

We need the tests to be in separate files, and to be really simple to write, inspired by cypress and mocha.

For now I load the tests using require and it works well, but for the tests to be even more simple, I'd like to remove the first and last lines which are redundant.

What I have

// test.file.js :
const test = new (require("../index.js").Test)()
// to be removed

test.this(true)   // test is initialized
  .should("equal", true)  // (syntax inspired by cypress)

test.log(__dirname) 
// all node functionalities (require...)
// are available

module.exports = test // to be removed
// importing logic :
function run (filePath) {
  const test = require(filePath)
  test.run()
} // much simplified

Here everything works fine but you need to require the library and do some logic and export the test in each test file.

What I would like

// test.file.js
test.log("hello world")

test.this(true)   // test is initialized
  .should("equal", true)

test.log(__dirname)  // node functions are available

Here test is already declared, initialized and available at scope level.

Exports are also automatic.

The problem

I'd like all node core functionalities to be available in the test BUT not the scope variables where I import the file.

What I tried

I achieved to make it work using some complicated eval, but the problem is that every local variables of the parent scopes are available for modification in the tests...

let hello = "world"
// this variable should not be available in the test

function run (testPath) {
  const test = new Test()
  // initialize the test

  const script = readFileSync(
    resolve(__dirname, testPath),
    {encoding: "utf-8"}
  )
  const scope = {
    test
  } // local variables i want the function to have access to

  eval(
    "(function ("
    + Object.keys(scope).join(", ")
    + ") {"
    + script
    +  "})"
  )(...Object.values(scope))

  console.log(hello)
  // here hello can be modified inside the test
  // this I don't want

  test.run() // works
}

I also tried with new Function(script) and some derivative but I could not find a solution

Question

How could I evaluate the test file in a clean node scope, scope with ALL the node functionalities AND only one shared variable which is test ?

Minimum reproducible example:

// test.file.js

test.log("hello from test")
test.variableProperty = "MODIFIED"
test.log(__dirname) // global node available
// test.log(hello) // should crash or hello be undefined
// index.js

class Test {
  constructor () {
    this.variableProperty = "INITIAL"
  }
  log (str) {
    console.log(str)
  }
  run () {
    console.log(this.variableProperty)
  }
}

// file import logic :
function importTest (path) {
  const test = new Test()
  // ...
  return test
}

// testing :
let hello = "should not appear"
const t = importTest("./test.file.js")
t.run()
// should output:
// "hello from test"
// (the __dirname)
// "MODIFIED"
// (and crash if last line of test.file.js uncommented)

Upvotes: 0

Views: 62

Answers (0)

Related Questions