This pull request introduces a set of Node.js scripts for performing A/B comparison of websteps and webconnectivity as described in https://github.com/ooni/probe/issues/1805. Rather than using Jafar, I ended up using `miniooni`'s `--censor` command line flag introduced in [v3.12.0-alpha.1](https://github.com/ooni/probe-cli/releases/tag/v3.12.0-alpha.1). The main reason for doing so is that it's simpler to run tests without requiring root access and Linux _and_ Docker (e.g., I did not develop part of this diff using Linux). Additionally, I choose to use Node.js rather than extending the existing Python framework for QA, because I found Node.js easier when working with JSON data.
		
			
				
	
	
		
			197 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // This file contains code for analysing results.
 | |
| 
 | |
| import { type } from "os"
 | |
| 
 | |
| // checkAnnotations checks whether we have annotations.
 | |
| function checkAnnotations(report) {
 | |
|     let result = true
 | |
| 
 | |
|     const isObject = typeof (report.annotations) === "object"
 | |
|     console.log(`checking whether annotations is an object... ${isObject}`)
 | |
|     result = result && isObject
 | |
| 
 | |
|     const hasArchitecture = typeof (report.annotations.architecture) === "string"
 | |
|     console.log(`checking whether annotations contains architecture... ${hasArchitecture}`)
 | |
|     result = result && hasArchitecture
 | |
| 
 | |
|     const hasEngineName = typeof (report.annotations.engine_name) === "string"
 | |
|     console.log(`checking whether annotations contains engine_name... ${hasEngineName}`)
 | |
|     result = result && hasEngineName
 | |
| 
 | |
|     const hasEngineVersion = typeof (report.annotations.engine_version) === "string"
 | |
|     console.log(`checking whether annotations contains engine_version... ${hasEngineVersion}`)
 | |
|     result = result && hasEngineVersion
 | |
| 
 | |
|     const hasPlatform = typeof (report.annotations.platform) === "string"
 | |
|     console.log(`checking whether annotations contains platform... ${hasPlatform}`)
 | |
|     result = result && hasPlatform
 | |
| 
 | |
|     return result
 | |
| }
 | |
| 
 | |
| // checkDataFormatVersion checks whether we have data_format_version.
 | |
| function checkDataFormatVersion(report) {
 | |
|     const result = report.data_format_version === "0.2.0"
 | |
|     console.log(`checking whether we have the right data format version... ${result}`)
 | |
|     return result
 | |
| }
 | |
| 
 | |
| // checkExtensions ensures that extensions exists and has the right type.
 | |
| function checkExtensions(report) {
 | |
|     const result = typeof (report.extensions) === "object"
 | |
|     console.log(`checking whether report.extensions is an object... ${result}`)
 | |
|     // Quirk: some experiments (e.g. web_connectivity) don't include
 | |
|     // an extensions object, and we don't want to fail here.
 | |
|     return true
 | |
| }
 | |
| 
 | |
| // checkInput ensures that the input is correct.
 | |
| function checkInput(testCase, report) {
 | |
|     const result = testCase.input === report.input
 | |
|     console.log(`checking whether input is correct... ${result}`)
 | |
|     return result
 | |
| }
 | |
| 
 | |
| // checkExperimentName ensures that the experimentName is correctly
 | |
| // set in the output report file.
 | |
| function checkExperimentName(name, report) {
 | |
|     const result = report.test_name === name
 | |
|     console.log(`checking whether the experiment name is correct... ${result}`)
 | |
|     return result
 | |
| }
 | |
| 
 | |
| // checkStartTime ensures that a given start time is in the correct format.
 | |
| function checkStartTime(report, key) {
 | |
|     const value = report[key]
 | |
|     let result = typeof (value) === "string"
 | |
|     console.log(`checking whether ${key} is a string... ${result}`)
 | |
|     result = result && value.match(/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/) !== null
 | |
|     console.log(`checking whether ${key} matches the date regexp... ${result}`)
 | |
|     return result
 | |
| }
 | |
| 
 | |
| // checkASN ensures that an ASN field is correct.
 | |
| function checkASN(report, key) {
 | |
|     const value = report[key]
 | |
|     let result = typeof (value) === "string"
 | |
|     console.log(`checking whether ${key} is a string... ${result}`)
 | |
|     result = result && value.match(/^AS[0-9]+$/) !== null
 | |
|     console.log(`checking whether ${key} matches the ASN regexp... ${result}`)
 | |
|     return result
 | |
| }
 | |
| 
 | |
| // checkProbeCC ensures that the probe_cc field is correct.
 | |
| function checkProbeCC(report) {
 | |
|     const value = report["probe_cc"]
 | |
|     let result = typeof (value) === "string"
 | |
|     console.log(`checking whether probe_cc is a string... ${result}`)
 | |
|     result = result && value.match(/^[A-Z]{2}$/) !== null
 | |
|     console.log(`checking whether probe_cc matches the CC regexp... ${result}`)
 | |
|     return result
 | |
| 
 | |
| }
 | |
| 
 | |
| // checkProbeIP ensures that the probe_ip field is correct.
 | |
| function checkProbeIP(report) {
 | |
|     const result = report.probe_ip === "127.0.0.1"
 | |
|     console.log(`checking whether probe_ip is correct... ${result}`)
 | |
|     return result
 | |
| }
 | |
| 
 | |
| // checkReportID ensures that the report_id field is correct.
 | |
| function checkReportID(report) {
 | |
|     const result = report.report_id === "" // note: we don't submit
 | |
|     console.log(`checking whether report_id is correct... ${result}`)
 | |
|     return result
 | |
| }
 | |
| 
 | |
| // checkString ensures that a field is a string.
 | |
| function checkString(report, key) {
 | |
|     const value = report[key]
 | |
|     const result = typeof(value) === "string"
 | |
|     console.log(`checking whether ${key} is a string... ${result}`)
 | |
|     return result
 | |
| }
 | |
| 
 | |
| // checkNetworkName ensures that an xxx_network_name field is correct.
 | |
| function checkNetworkName(report, key) {
 | |
|     return checkString(report, key)
 | |
| }
 | |
| 
 | |
| // checkResolverIP ensures that the resolver_ip field is correct.
 | |
| function checkResolverIP(report) {
 | |
|     return checkString(report, "resolver_ip")
 | |
| }
 | |
| 
 | |
| // checkSoftwareName ensures that the software_name field is correct.
 | |
| function checkSoftwareName(report) {
 | |
|     return checkString(report, "software_name")
 | |
| }
 | |
| 
 | |
| // checkSoftwareVersion ensures that the software_version field is correct.
 | |
| function checkSoftwareVersion(report) {
 | |
|     return checkString(report, "software_version")
 | |
| }
 | |
| 
 | |
| // checkTestRuntime ensures that the test_runtime field is correct.
 | |
| function checkTestRuntime(report) {
 | |
|     const result = typeof(report.test_runtime) === "number"
 | |
|     console.log(`checking whether test_runtime is a number... ${result}`)
 | |
|     return result
 | |
| }
 | |
| 
 | |
| // checkTestVersion ensures that the test_version field is correct.
 | |
| function checkTestVersion(report) {
 | |
|     return checkString(report, "test_version")
 | |
| }
 | |
| 
 | |
| // checkTestKeys ensures that test_keys is an object.
 | |
| function checkTestKeys(report) {
 | |
|     const result = typeof(report.test_keys) === "object"
 | |
|     console.log(`checking whether test_keys is an object... ${result}`)
 | |
|     return result
 | |
| }
 | |
| 
 | |
| // checkMeasurement is a function that invokes a standard set of checks
 | |
| // to validate the top-level test keys of a result.
 | |
| //
 | |
| // This function helps to implement per-experiment checkers.
 | |
| //
 | |
| // Arguments:
 | |
| //
 | |
| // - testCase is the current test case
 | |
| //
 | |
| // - name is the name of the current experiment
 | |
| //
 | |
| // - report is the JSON measurement
 | |
| //
 | |
| // - extraChecks is the optional callback to perform extra checks
 | |
| // that takes in input the report's test keys.
 | |
| export function checkMeasurement(testCase, name, report, extraChecks) {
 | |
|     let result = true
 | |
|     result = result && checkAnnotations(report)
 | |
|     result = result && checkDataFormatVersion(report)
 | |
|     result = result && checkExtensions(report)
 | |
|     result = result && checkInput(testCase, report)
 | |
|     result = result && checkStartTime(report, "measurement_start_time")
 | |
|     result = result && checkASN(report, "probe_asn")
 | |
|     result = result && checkProbeCC(report)
 | |
|     result = result && checkProbeIP(report)
 | |
|     result = result && checkNetworkName(report, "probe_network_name")
 | |
|     result = result && checkReportID(report)
 | |
|     result = result && checkASN(report, "resolver_asn")
 | |
|     result = result && checkResolverIP(report)
 | |
|     result = result && checkNetworkName(report, "resolver_network_name")
 | |
|     result = result && checkSoftwareName(report)
 | |
|     result = result && checkSoftwareVersion(report)
 | |
|     result = result && checkExperimentName(name, report)
 | |
|     result = result && checkTestRuntime(report)
 | |
|     result = result && checkStartTime(report, "test_start_time")
 | |
|     result = result && checkTestVersion(report)
 | |
|     result = result && checkTestKeys(report)
 | |
|     if (typeof extraChecks === "function") {
 | |
|         result = result && extraChecks(report.test_keys)
 | |
|     }
 | |
|     return result
 | |
| }
 |