// This file contains code for running test cases. import child_process from "child_process" import crypto from "crypto" import fs from "fs" import path from "path" // tempFile returns the name of a temporary file. This function is // not as secure as using mktemp but it does not matter in this // context. We just need to create file names in the local directory // with enough entropy that every run has a different name. // // See https://stackoverflow.com/questions/7055061 for an insightful // discussion about creating temporary files in node. function tempFile(suffix) { return path.join(`tmp-${crypto.randomBytes(16).toString('hex')}.${suffix}`) } // exec executes a command. This function throws on failure. The stdout // and stderr should be console.log-ed once the command returns. function exec(command) { console.log(`+ ${command}`) child_process.execSync(command) } // writeCensorJsonFile writes a censor.json file using a file name // containing random characters and returns the file name. function writeCensorJsonFile(testCase) { const fileName = tempFile("json") fs.writeFileSync(fileName, JSON.stringify(testCase.blocking)) return fileName } // readReportFile reads and parses the report file, thus returning // the JSON object contained inside the report file. function readReportFile(reportFile) { const data = fs.readFileSync(reportFile, { "encoding": "utf-8" }) return JSON.parse(data) } // runExperiment runs the given test case with the given experiment. function runExperiment(testCase, experiment, checker) { console.log(`## running: ${testCase.name}.${experiment}`) console.log("") const censorJson = writeCensorJsonFile(testCase) const reportJson = tempFile("json") // Note: using -n because we don't want to submit QA checks. exec(`./miniooni -n --censor ${censorJson} -o ${reportJson} -i ${testCase.input} ${experiment}`) console.log("") // TODO(bassosimone): support multiple entries per file const report = readReportFile(reportJson) const analysisResult = checker(testCase, experiment, report) console.log("") console.log("") if (analysisResult !== true && analysisResult !== false) { console.log("the analysis function returned neither true nor false") process.exit(1) } return analysisResult } // recompileMiniooni recompiles miniooni if needed. function recompileMiniooni() { exec("go build -v ./internal/cmd/miniooni") } // runTestCase runs the given test case. // // A test case is an object with the following fields: // // - name (string): the name of the test case; // // - description (string): a description of the test case; // // - input (string): the input to pass to the experiment; // // - blocking (object): a blocking specification (i.e., the // serialization of a filtering.TProxyConfig struct); // // - experiments (object): the keys are names of nettests // to run and the values are functions taking as their // unique argument the experiment's test_keys. export function runTestCase(testCase) { recompileMiniooni() console.log("") console.log(`# running: ${testCase.name}`) let result = true for (const [name, checker] of Object.entries(testCase.experiments)) { result = result && runExperiment(testCase, name, checker) } return result }