<commit_before>'use strict';

const child_process = require('child_process'),
	libpath = require('path'),
	fs = require('fs'),
	EventEmitter = require('events').EventEmitter,
	rollup = require('./rollupRules.js');

class Suite {
	constructor(name){
		this.name = name;
		this.status = ''; // in ['cancelled', 'passed', 'failed']
		this.parent = null;
		this.tests = [];
	}
	addTest(test){
		this.tests.push(test);
		test.parent = this;
	}
}

class TestRunner extends EventEmitter
{
	constructor(name, owner, flags, lrsSettingsUUID,options, rollupRule)
	{
		super();

		this.proc = null;

		this.name = name;
		this.owner = owner;
		this.flags = flags;
		this.options = options;
		this.lrsSettingsUUID = lrsSettingsUUID;
		this.rollupRule = rollup[rollupRule] ? rollupRule : 'mustPassAll';

		this.uuid = require('uuid').v4();
		this.startTime = null;
		this.endTime = null;
		this.duration = null;
		this.state = 'notStarted'; // in ["notStarted", "started", "finished", "cancelled"]

		this.summary = {
			total: null,
			passed: null,
			failed: null
		};

		this.log = null;

		this.activeTest = null;
	}

	start()
	{
		if(this.state !== 'notStarted') return;
		this.state = 'started';

		// spin up the child process
		this.proc = child_process.fork( libpath.join(__dirname, "lrs-test.js"),
			["--debug"],
			{
				execArgv:[/*"--debug-brk=5959"*/],
				cwd: libpath.join(__dirname,"/../")
			}
		);
	
		// hook up listeners
		this._registerStatusUpdates();

		// kick off tests when ready
		this.proc.on('message', function(msg)
		{
			if(msg.action === 'ready'){
				this.proc.send({action: 'runTests', payload: this.flags});
			}
		}.bind(this));
	}

	_registerStatusUpdates()
	{
		this.proc.on('message', function(msg)
		{
			console.log(msg);
			var action = msg.action, payload = msg.payload;
			switch(action)
			{
			case 'start':

				// initialize counters
				this.summary.total = payload;
				this.summary.passed = 0;
				this.summary.failed = 0;
				this.startTime = Date.now();
				break;

			case 'end':

				this.endTime = Date.now();
				this.duration = this.endTime - this.startTime;
				this.state = 'finished';
				break;

			case 'suite start':

				// start a new suite
				var newSuite = new Suite(payload);

				// add to log
				if(this.activeTest)
					this.activeTest.addTest(newSuite);
				else
					this.log = newSuite;

				this.activeTest = newSuite;
				break;

			case 'suite end':

				// finish the suite
				if(this.activeTest.name === payload)
				{
					// roll up test status
					this.activeTest.status = rollup[this.rollupRule](this.activeTest);

					// move test cursor
					this.activeTest = this.activeTest.parent;
				}
				else
					console.error('Dangling suite end!', this.activeTest.name);
				break;
				
			case 'test start':

				// start a new test
				var newTest = new Suite(payload);

				// add to log
				if(this.activeTest)
					this.activeTest.addTest(newTest);
				else
					this.log = newTest;

				this.activeTest = newTest;
				break;

			case 'test end':

				if(this.activeTest.name === payload)
					this.activeTest = this.activeTest.parent;
				else
					console.error('Dangling test end!', this.activeTest.name);
				break;

			case 'test pass':
				this.activeTest.status = 'passed';
				this.summary.passed++;
				break;

			case 'test fail':
				this.activeTest.status = 'failed';
				this.activeTest.error = payload.message;
				this.summary.failed++;
				break;
			};

			// pass along the event
			this.emit('message', msg);

		}.bind(this));
	}

	cancel()
	{
		if(this.proc)
		{
			this.proc.kill();
			this.endTime = Date.now();
			this.duration = this.endTime - this.startTime;

			// evaluate all in-progress suites to "cancelled"
			this.state = 'cancelled';
			this.activeTest.status = 'cancelled';
			this.activeTest = this.activeTest.parent;
			while(this.activeTest){
				this.activeTest.status = rollup[this.rollupRule](this.activeTest);
				this.emit('message', {action: 'suite end', payload: this.activeTest.name});
				this.activeTest = this.activeTest.parent;
			}

			this.emit('message', {action: 'end'});
			this.emit('close');
		}
	}

	getCleanRecord()
	{
		var runRecord = {
			name: this.name || null,
			owner: this.owner || null,
			flags: {
				endpoint: this.flags.endpoint,
				basicAuth: this.flags.basicAuth,
				authUser: this.flags.authUser,
				oAuth1: this.flags.oAuth1,
				consumer_key: this.flags.consumer_key
			},
			options:this.options,
			lrsSettingsUUID: this.lrsSettingsUUID,
			rollupRule: this.rollupRule,

			uuid: this.uuid,
			startTime: this.startTime,
			endTime: this.endTime,
			duration: this.duration,
			state: this.state,
			summary: {
				total: this.summary.total,
				passed: this.summary.passed,
				failed: this.summary.failed
			}
		};

		function cleanLog(log)
		{
			if(!log) return null;

			return {
				name: log.name,
				status: log.status,
				error: log.error,
				tests: log.tests.map(cleanLog)
			};
		}

		runRecord.log = cleanLog(this.log);
		return runRecord;
	}
}


exports.testRunner = TestRunner;
<commit_msg>fix rare and tricky bug that occurs when the cancel method has been called, but the child process gets a message out before it is killed<commit_after>'use strict';

const child_process = require('child_process'),
	libpath = require('path'),
	fs = require('fs'),
	EventEmitter = require('events').EventEmitter,
	rollup = require('./rollupRules.js');

class Suite {
	constructor(name){
		this.name = name;
		this.status = ''; // in ['cancelled', 'passed', 'failed']
		this.parent = null;
		this.tests = [];
	}
	addTest(test){
		this.tests.push(test);
		test.parent = this;
	}
}

class TestRunner extends EventEmitter
{
	constructor(name, owner, flags, lrsSettingsUUID,options, rollupRule)
	{
		super();

		this.proc = null;

		this.name = name;
		this.owner = owner;
		this.flags = flags;
		this.options = options;
		this.lrsSettingsUUID = lrsSettingsUUID;
		this.rollupRule = rollup[rollupRule] ? rollupRule : 'mustPassAll';

		this.uuid = require('uuid').v4();
		this.startTime = null;
		this.endTime = null;
		this.duration = null;
		this.state = 'notStarted'; // in ["notStarted", "started", "finished", "cancelled"]

		this.summary = {
			total: null,
			passed: null,
			failed: null
		};

		this.log = null;

		this.activeTest = null;
	}

	start()
	{
		if(this.state !== 'notStarted') return;
		this.state = 'started';

		// spin up the child process
		this.proc = child_process.fork( libpath.join(__dirname, "lrs-test.js"),
			["--debug"],
			{
				execArgv:[/*"--debug-brk=5959"*/],
				cwd: libpath.join(__dirname,"/../")
			}
		);
	
		// hook up listeners
		this._registerStatusUpdates();

		// kick off tests when ready
		this.proc.on('message', function(msg)
		{
			if(msg.action === 'ready'){
				this.proc.send({action: 'runTests', payload: this.flags});
			}
		}.bind(this));
	}

	_registerStatusUpdates()
	{
		this.proc.on('message', function(msg)
		{
			
			var action = msg.action, payload = msg.payload;
			switch(action)
			{
			case 'start':

				// initialize counters
				this.summary.total = payload;
				this.summary.passed = 0;
				this.summary.failed = 0;
				this.startTime = Date.now();
				break;

			case 'end':

				this.endTime = Date.now();
				this.duration = this.endTime - this.startTime;
				this.state = 'finished';
				break;

			case 'suite start':

				// start a new suite
				var newSuite = new Suite(payload);

				// add to log
				if(this.activeTest)
					this.activeTest.addTest(newSuite);
				else
					this.log = newSuite;

				this.activeTest = newSuite;
				break;

			case 'suite end':

				if(this.activeTest)
				{
					// finish the suite
					if(this.activeTest.name === payload)
					{
						// roll up test status
						this.activeTest.status = rollup[this.rollupRule](this.activeTest);

						// move test cursor
						this.activeTest = this.activeTest.parent;
					}
					else
						console.error('Dangling suite end!', this.activeTest.name);
				}
				break;
				
			case 'test start':

				// start a new test
				var newTest = new Suite(payload);

				// add to log
				if(this.activeTest)
					this.activeTest.addTest(newTest);
				else
					this.log = newTest;

				this.activeTest = newTest;
				break;

			case 'test end':

				if(this.activeTest)
				{
					if(this.activeTest.name === payload)
						this.activeTest = this.activeTest.parent;
					else
						console.error('Dangling test end!', this.activeTest.name);
					break;
				}

			case 'test pass':
				if(this.activeTest)
				{
					this.activeTest.status = 'passed';
					this.summary.passed++;
				}
				break;

			case 'test fail':
				if(this.activeTest){ //careful - cancel can blank this, then the message comes in
					this.activeTest.status = 'failed';
					this.activeTest.error = payload.message;
					this.summary.failed++;
				}
				break;
			};

			// pass along the event
			this.emit('message', msg);

		}.bind(this));
	}

	cancel()
	{
		if(this.proc)
		{
			this.proc.kill();
			this.endTime = Date.now();
			this.duration = this.endTime - this.startTime;

			// evaluate all in-progress suites to "cancelled"
			this.state = 'cancelled';
			this.activeTest.status = 'cancelled';
			this.activeTest = this.activeTest.parent;
			while(this.activeTest){
				this.activeTest.status = rollup[this.rollupRule](this.activeTest);
				this.emit('message', {action: 'suite end', payload: this.activeTest.name});
				this.activeTest = this.activeTest.parent;
			}

			this.emit('message', {action: 'end'});
			this.emit('close');
		}
	}

	getCleanRecord()
	{
		var runRecord = {
			name: this.name || null,
			owner: this.owner || null,
			flags: {
				endpoint: this.flags.endpoint,
				basicAuth: this.flags.basicAuth,
				authUser: this.flags.authUser,
				oAuth1: this.flags.oAuth1,
				consumer_key: this.flags.consumer_key
			},
			options:this.options,
			lrsSettingsUUID: this.lrsSettingsUUID,
			rollupRule: this.rollupRule,

			uuid: this.uuid,
			startTime: this.startTime,
			endTime: this.endTime,
			duration: this.duration,
			state: this.state,
			summary: {
				total: this.summary.total,
				passed: this.summary.passed,
				failed: this.summary.failed
			}
		};

		function cleanLog(log)
		{
			if(!log) return null;

			return {
				name: log.name,
				status: log.status,
				error: log.error,
				tests: log.tests.map(cleanLog)
			};
		}

		runRecord.log = cleanLog(this.log);
		return runRecord;
	}
}


exports.testRunner = TestRunner;

{'fraction_non_alphanumeric': 0.14674952198852773, 'fraction_numerical': 0.0017208413001912047, 'mean_word_length': 2.8601476014760148, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3345654', 'n_tokens_mistral': 4438, 'n_tokens_neox': 3535, 'n_words': 746}