11cb0ef41Sopenharmony_ci// Copyright Joyent, Inc. and other Node contributors. 21cb0ef41Sopenharmony_ci// 31cb0ef41Sopenharmony_ci// Permission is hereby granted, free of charge, to any person obtaining a 41cb0ef41Sopenharmony_ci// copy of this software and associated documentation files (the 51cb0ef41Sopenharmony_ci// "Software"), to deal in the Software without restriction, including 61cb0ef41Sopenharmony_ci// without limitation the rights to use, copy, modify, merge, publish, 71cb0ef41Sopenharmony_ci// distribute, sublicense, and/or sell copies of the Software, and to permit 81cb0ef41Sopenharmony_ci// persons to whom the Software is furnished to do so, subject to the 91cb0ef41Sopenharmony_ci// following conditions: 101cb0ef41Sopenharmony_ci// 111cb0ef41Sopenharmony_ci// The above copyright notice and this permission notice shall be included 121cb0ef41Sopenharmony_ci// in all copies or substantial portions of the Software. 131cb0ef41Sopenharmony_ci// 141cb0ef41Sopenharmony_ci// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 151cb0ef41Sopenharmony_ci// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 161cb0ef41Sopenharmony_ci// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 171cb0ef41Sopenharmony_ci// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 181cb0ef41Sopenharmony_ci// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 191cb0ef41Sopenharmony_ci// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 201cb0ef41Sopenharmony_ci// USE OR OTHER DEALINGS IN THE SOFTWARE. 211cb0ef41Sopenharmony_ci 221cb0ef41Sopenharmony_ci'use strict'; 231cb0ef41Sopenharmony_ciconst common = require('../common'); 241cb0ef41Sopenharmony_ci 251cb0ef41Sopenharmony_ciif (!common.hasCrypto) 261cb0ef41Sopenharmony_ci common.skip('missing crypto'); 271cb0ef41Sopenharmony_ci 281cb0ef41Sopenharmony_ciif (!common.opensslCli) 291cb0ef41Sopenharmony_ci common.skip('node compiled without OpenSSL CLI.'); 301cb0ef41Sopenharmony_ci 311cb0ef41Sopenharmony_ci// This is a rather complex test which sets up various TLS servers with node 321cb0ef41Sopenharmony_ci// and connects to them using the 'openssl s_client' command line utility 331cb0ef41Sopenharmony_ci// with various keys. Depending on the certificate authority and other 341cb0ef41Sopenharmony_ci// parameters given to the server, the various clients are 351cb0ef41Sopenharmony_ci// - rejected, 361cb0ef41Sopenharmony_ci// - accepted and "unauthorized", or 371cb0ef41Sopenharmony_ci// - accepted and "authorized". 381cb0ef41Sopenharmony_ci 391cb0ef41Sopenharmony_ciconst assert = require('assert'); 401cb0ef41Sopenharmony_ciconst { spawn } = require('child_process'); 411cb0ef41Sopenharmony_ciconst { SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION } = 421cb0ef41Sopenharmony_ci require('crypto').constants; 431cb0ef41Sopenharmony_ciconst tls = require('tls'); 441cb0ef41Sopenharmony_ciconst fixtures = require('../common/fixtures'); 451cb0ef41Sopenharmony_ci 461cb0ef41Sopenharmony_ciconst testCases = 471cb0ef41Sopenharmony_ci [{ title: 'Do not request certs. Everyone is unauthorized.', 481cb0ef41Sopenharmony_ci requestCert: false, 491cb0ef41Sopenharmony_ci rejectUnauthorized: false, 501cb0ef41Sopenharmony_ci renegotiate: false, 511cb0ef41Sopenharmony_ci CAs: ['ca1-cert'], 521cb0ef41Sopenharmony_ci clients: 531cb0ef41Sopenharmony_ci [{ name: 'agent1', shouldReject: false, shouldAuth: false }, 541cb0ef41Sopenharmony_ci { name: 'agent2', shouldReject: false, shouldAuth: false }, 551cb0ef41Sopenharmony_ci { name: 'agent3', shouldReject: false, shouldAuth: false }, 561cb0ef41Sopenharmony_ci { name: 'nocert', shouldReject: false, shouldAuth: false }, 571cb0ef41Sopenharmony_ci ] }, 581cb0ef41Sopenharmony_ci 591cb0ef41Sopenharmony_ci { title: 'Allow both authed and unauthed connections with CA1', 601cb0ef41Sopenharmony_ci requestCert: true, 611cb0ef41Sopenharmony_ci rejectUnauthorized: false, 621cb0ef41Sopenharmony_ci renegotiate: false, 631cb0ef41Sopenharmony_ci CAs: ['ca1-cert'], 641cb0ef41Sopenharmony_ci clients: 651cb0ef41Sopenharmony_ci [{ name: 'agent1', shouldReject: false, shouldAuth: true }, 661cb0ef41Sopenharmony_ci { name: 'agent2', shouldReject: false, shouldAuth: false }, 671cb0ef41Sopenharmony_ci { name: 'agent3', shouldReject: false, shouldAuth: false }, 681cb0ef41Sopenharmony_ci { name: 'nocert', shouldReject: false, shouldAuth: false }, 691cb0ef41Sopenharmony_ci ] }, 701cb0ef41Sopenharmony_ci 711cb0ef41Sopenharmony_ci { title: 'Do not request certs at connection. Do that later', 721cb0ef41Sopenharmony_ci requestCert: false, 731cb0ef41Sopenharmony_ci rejectUnauthorized: false, 741cb0ef41Sopenharmony_ci renegotiate: true, 751cb0ef41Sopenharmony_ci CAs: ['ca1-cert'], 761cb0ef41Sopenharmony_ci clients: 771cb0ef41Sopenharmony_ci [{ name: 'agent1', shouldReject: false, shouldAuth: true }, 781cb0ef41Sopenharmony_ci { name: 'agent2', shouldReject: false, shouldAuth: false }, 791cb0ef41Sopenharmony_ci { name: 'agent3', shouldReject: false, shouldAuth: false }, 801cb0ef41Sopenharmony_ci { name: 'nocert', shouldReject: false, shouldAuth: false }, 811cb0ef41Sopenharmony_ci ] }, 821cb0ef41Sopenharmony_ci 831cb0ef41Sopenharmony_ci { title: 'Allow only authed connections with CA1', 841cb0ef41Sopenharmony_ci requestCert: true, 851cb0ef41Sopenharmony_ci rejectUnauthorized: true, 861cb0ef41Sopenharmony_ci renegotiate: false, 871cb0ef41Sopenharmony_ci CAs: ['ca1-cert'], 881cb0ef41Sopenharmony_ci clients: 891cb0ef41Sopenharmony_ci [{ name: 'agent1', shouldReject: false, shouldAuth: true }, 901cb0ef41Sopenharmony_ci { name: 'agent2', shouldReject: true }, 911cb0ef41Sopenharmony_ci { name: 'agent3', shouldReject: true }, 921cb0ef41Sopenharmony_ci { name: 'nocert', shouldReject: true }, 931cb0ef41Sopenharmony_ci ] }, 941cb0ef41Sopenharmony_ci 951cb0ef41Sopenharmony_ci { title: 'Allow only authed connections with CA1 and CA2', 961cb0ef41Sopenharmony_ci requestCert: true, 971cb0ef41Sopenharmony_ci rejectUnauthorized: true, 981cb0ef41Sopenharmony_ci renegotiate: false, 991cb0ef41Sopenharmony_ci CAs: ['ca1-cert', 'ca2-cert'], 1001cb0ef41Sopenharmony_ci clients: 1011cb0ef41Sopenharmony_ci [{ name: 'agent1', shouldReject: false, shouldAuth: true }, 1021cb0ef41Sopenharmony_ci { name: 'agent2', shouldReject: true }, 1031cb0ef41Sopenharmony_ci { name: 'agent3', shouldReject: false, shouldAuth: true }, 1041cb0ef41Sopenharmony_ci { name: 'nocert', shouldReject: true }, 1051cb0ef41Sopenharmony_ci ] }, 1061cb0ef41Sopenharmony_ci 1071cb0ef41Sopenharmony_ci 1081cb0ef41Sopenharmony_ci { title: 'Allow only certs signed by CA2 but not in the CRL', 1091cb0ef41Sopenharmony_ci requestCert: true, 1101cb0ef41Sopenharmony_ci rejectUnauthorized: true, 1111cb0ef41Sopenharmony_ci renegotiate: false, 1121cb0ef41Sopenharmony_ci CAs: ['ca2-cert'], 1131cb0ef41Sopenharmony_ci crl: 'ca2-crl', 1141cb0ef41Sopenharmony_ci clients: [ 1151cb0ef41Sopenharmony_ci { name: 'agent1', shouldReject: true, shouldAuth: false }, 1161cb0ef41Sopenharmony_ci { name: 'agent2', shouldReject: true, shouldAuth: false }, 1171cb0ef41Sopenharmony_ci { name: 'agent3', shouldReject: false, shouldAuth: true }, 1181cb0ef41Sopenharmony_ci // Agent4 has a cert in the CRL. 1191cb0ef41Sopenharmony_ci { name: 'agent4', shouldReject: true, shouldAuth: false }, 1201cb0ef41Sopenharmony_ci { name: 'nocert', shouldReject: true }, 1211cb0ef41Sopenharmony_ci ] }, 1221cb0ef41Sopenharmony_ci ]; 1231cb0ef41Sopenharmony_ci 1241cb0ef41Sopenharmony_cifunction filenamePEM(n) { 1251cb0ef41Sopenharmony_ci return fixtures.path('keys', `${n}.pem`); 1261cb0ef41Sopenharmony_ci} 1271cb0ef41Sopenharmony_ci 1281cb0ef41Sopenharmony_cifunction loadPEM(n) { 1291cb0ef41Sopenharmony_ci return fixtures.readKey(`${n}.pem`); 1301cb0ef41Sopenharmony_ci} 1311cb0ef41Sopenharmony_ci 1321cb0ef41Sopenharmony_ci 1331cb0ef41Sopenharmony_ciconst serverKey = loadPEM('agent2-key'); 1341cb0ef41Sopenharmony_ciconst serverCert = loadPEM('agent2-cert'); 1351cb0ef41Sopenharmony_ci 1361cb0ef41Sopenharmony_ci 1371cb0ef41Sopenharmony_cifunction runClient(prefix, port, options, cb) { 1381cb0ef41Sopenharmony_ci 1391cb0ef41Sopenharmony_ci // Client can connect in three ways: 1401cb0ef41Sopenharmony_ci // - Self-signed cert 1411cb0ef41Sopenharmony_ci // - Certificate, but not signed by CA. 1421cb0ef41Sopenharmony_ci // - Certificate signed by CA. 1431cb0ef41Sopenharmony_ci 1441cb0ef41Sopenharmony_ci const args = ['s_client', '-connect', `127.0.0.1:${port}`]; 1451cb0ef41Sopenharmony_ci 1461cb0ef41Sopenharmony_ci console.log(`${prefix} connecting with`, options.name); 1471cb0ef41Sopenharmony_ci 1481cb0ef41Sopenharmony_ci switch (options.name) { 1491cb0ef41Sopenharmony_ci case 'agent1': 1501cb0ef41Sopenharmony_ci // Signed by CA1 1511cb0ef41Sopenharmony_ci args.push('-key'); 1521cb0ef41Sopenharmony_ci args.push(filenamePEM('agent1-key')); 1531cb0ef41Sopenharmony_ci args.push('-cert'); 1541cb0ef41Sopenharmony_ci args.push(filenamePEM('agent1-cert')); 1551cb0ef41Sopenharmony_ci break; 1561cb0ef41Sopenharmony_ci 1571cb0ef41Sopenharmony_ci case 'agent2': 1581cb0ef41Sopenharmony_ci // Self-signed 1591cb0ef41Sopenharmony_ci // This is also the key-cert pair that the server will use. 1601cb0ef41Sopenharmony_ci args.push('-key'); 1611cb0ef41Sopenharmony_ci args.push(filenamePEM('agent2-key')); 1621cb0ef41Sopenharmony_ci args.push('-cert'); 1631cb0ef41Sopenharmony_ci args.push(filenamePEM('agent2-cert')); 1641cb0ef41Sopenharmony_ci break; 1651cb0ef41Sopenharmony_ci 1661cb0ef41Sopenharmony_ci case 'agent3': 1671cb0ef41Sopenharmony_ci // Signed by CA2 1681cb0ef41Sopenharmony_ci args.push('-key'); 1691cb0ef41Sopenharmony_ci args.push(filenamePEM('agent3-key')); 1701cb0ef41Sopenharmony_ci args.push('-cert'); 1711cb0ef41Sopenharmony_ci args.push(filenamePEM('agent3-cert')); 1721cb0ef41Sopenharmony_ci break; 1731cb0ef41Sopenharmony_ci 1741cb0ef41Sopenharmony_ci case 'agent4': 1751cb0ef41Sopenharmony_ci // Signed by CA2 (rejected by ca2-crl) 1761cb0ef41Sopenharmony_ci args.push('-key'); 1771cb0ef41Sopenharmony_ci args.push(filenamePEM('agent4-key')); 1781cb0ef41Sopenharmony_ci args.push('-cert'); 1791cb0ef41Sopenharmony_ci args.push(filenamePEM('agent4-cert')); 1801cb0ef41Sopenharmony_ci break; 1811cb0ef41Sopenharmony_ci 1821cb0ef41Sopenharmony_ci case 'nocert': 1831cb0ef41Sopenharmony_ci // Do not send certificate 1841cb0ef41Sopenharmony_ci break; 1851cb0ef41Sopenharmony_ci 1861cb0ef41Sopenharmony_ci default: 1871cb0ef41Sopenharmony_ci throw new Error(`${prefix}Unknown agent name`); 1881cb0ef41Sopenharmony_ci } 1891cb0ef41Sopenharmony_ci 1901cb0ef41Sopenharmony_ci // To test use: openssl s_client -connect localhost:8000 1911cb0ef41Sopenharmony_ci const client = spawn(common.opensslCli, args); 1921cb0ef41Sopenharmony_ci 1931cb0ef41Sopenharmony_ci let out = ''; 1941cb0ef41Sopenharmony_ci 1951cb0ef41Sopenharmony_ci let rejected = true; 1961cb0ef41Sopenharmony_ci let authed = false; 1971cb0ef41Sopenharmony_ci let goodbye = false; 1981cb0ef41Sopenharmony_ci 1991cb0ef41Sopenharmony_ci client.stdout.setEncoding('utf8'); 2001cb0ef41Sopenharmony_ci client.stdout.on('data', function(d) { 2011cb0ef41Sopenharmony_ci out += d; 2021cb0ef41Sopenharmony_ci 2031cb0ef41Sopenharmony_ci if (!goodbye && /_unauthed/.test(out)) { 2041cb0ef41Sopenharmony_ci console.error(`${prefix} * unauthed`); 2051cb0ef41Sopenharmony_ci goodbye = true; 2061cb0ef41Sopenharmony_ci client.kill(); 2071cb0ef41Sopenharmony_ci authed = false; 2081cb0ef41Sopenharmony_ci rejected = false; 2091cb0ef41Sopenharmony_ci } 2101cb0ef41Sopenharmony_ci 2111cb0ef41Sopenharmony_ci if (!goodbye && /_authed/.test(out)) { 2121cb0ef41Sopenharmony_ci console.error(`${prefix} * authed`); 2131cb0ef41Sopenharmony_ci goodbye = true; 2141cb0ef41Sopenharmony_ci client.kill(); 2151cb0ef41Sopenharmony_ci authed = true; 2161cb0ef41Sopenharmony_ci rejected = false; 2171cb0ef41Sopenharmony_ci } 2181cb0ef41Sopenharmony_ci }); 2191cb0ef41Sopenharmony_ci 2201cb0ef41Sopenharmony_ci client.on('exit', function(code) { 2211cb0ef41Sopenharmony_ci if (options.shouldReject) { 2221cb0ef41Sopenharmony_ci assert.strictEqual( 2231cb0ef41Sopenharmony_ci rejected, true, 2241cb0ef41Sopenharmony_ci `${prefix}${options.name} NOT rejected, but should have been`); 2251cb0ef41Sopenharmony_ci } else { 2261cb0ef41Sopenharmony_ci assert.strictEqual( 2271cb0ef41Sopenharmony_ci rejected, false, 2281cb0ef41Sopenharmony_ci `${prefix}${options.name} rejected, but should NOT have been`); 2291cb0ef41Sopenharmony_ci assert.strictEqual( 2301cb0ef41Sopenharmony_ci authed, options.shouldAuth, 2311cb0ef41Sopenharmony_ci `${prefix}${options.name} authed is ${authed} but should have been ${ 2321cb0ef41Sopenharmony_ci options.shouldAuth}`); 2331cb0ef41Sopenharmony_ci } 2341cb0ef41Sopenharmony_ci 2351cb0ef41Sopenharmony_ci cb(); 2361cb0ef41Sopenharmony_ci }); 2371cb0ef41Sopenharmony_ci} 2381cb0ef41Sopenharmony_ci 2391cb0ef41Sopenharmony_ci 2401cb0ef41Sopenharmony_ci// Run the tests 2411cb0ef41Sopenharmony_cilet successfulTests = 0; 2421cb0ef41Sopenharmony_cifunction runTest(port, testIndex) { 2431cb0ef41Sopenharmony_ci const prefix = `${testIndex} `; 2441cb0ef41Sopenharmony_ci const tcase = testCases[testIndex]; 2451cb0ef41Sopenharmony_ci if (!tcase) return; 2461cb0ef41Sopenharmony_ci 2471cb0ef41Sopenharmony_ci console.error(`${prefix}Running '${tcase.title}'`); 2481cb0ef41Sopenharmony_ci 2491cb0ef41Sopenharmony_ci const cas = tcase.CAs.map(loadPEM); 2501cb0ef41Sopenharmony_ci 2511cb0ef41Sopenharmony_ci const crl = tcase.crl ? loadPEM(tcase.crl) : null; 2521cb0ef41Sopenharmony_ci 2531cb0ef41Sopenharmony_ci const serverOptions = { 2541cb0ef41Sopenharmony_ci key: serverKey, 2551cb0ef41Sopenharmony_ci cert: serverCert, 2561cb0ef41Sopenharmony_ci ca: cas, 2571cb0ef41Sopenharmony_ci crl: crl, 2581cb0ef41Sopenharmony_ci requestCert: tcase.requestCert, 2591cb0ef41Sopenharmony_ci rejectUnauthorized: tcase.rejectUnauthorized 2601cb0ef41Sopenharmony_ci }; 2611cb0ef41Sopenharmony_ci 2621cb0ef41Sopenharmony_ci // If renegotiating - session might be resumed and openssl won't request 2631cb0ef41Sopenharmony_ci // client's certificate (probably because of bug in the openssl) 2641cb0ef41Sopenharmony_ci if (tcase.renegotiate) { 2651cb0ef41Sopenharmony_ci serverOptions.secureOptions = 2661cb0ef41Sopenharmony_ci SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; 2671cb0ef41Sopenharmony_ci // Renegotiation as a protocol feature was dropped after TLS1.2. 2681cb0ef41Sopenharmony_ci serverOptions.maxVersion = 'TLSv1.2'; 2691cb0ef41Sopenharmony_ci } 2701cb0ef41Sopenharmony_ci 2711cb0ef41Sopenharmony_ci let renegotiated = false; 2721cb0ef41Sopenharmony_ci const server = tls.Server(serverOptions, function handleConnection(c) { 2731cb0ef41Sopenharmony_ci c.on('error', function(e) { 2741cb0ef41Sopenharmony_ci // child.kill() leads ECONNRESET error in the TLS connection of 2751cb0ef41Sopenharmony_ci // openssl s_client via spawn(). A test result is already 2761cb0ef41Sopenharmony_ci // checked by the data of client.stdout before child.kill() so 2771cb0ef41Sopenharmony_ci // these tls errors can be ignored. 2781cb0ef41Sopenharmony_ci }); 2791cb0ef41Sopenharmony_ci if (tcase.renegotiate && !renegotiated) { 2801cb0ef41Sopenharmony_ci renegotiated = true; 2811cb0ef41Sopenharmony_ci setTimeout(function() { 2821cb0ef41Sopenharmony_ci console.error(`${prefix}- connected, renegotiating`); 2831cb0ef41Sopenharmony_ci c.write('\n_renegotiating\n'); 2841cb0ef41Sopenharmony_ci return c.renegotiate({ 2851cb0ef41Sopenharmony_ci requestCert: true, 2861cb0ef41Sopenharmony_ci rejectUnauthorized: false 2871cb0ef41Sopenharmony_ci }, function(err) { 2881cb0ef41Sopenharmony_ci assert.ifError(err); 2891cb0ef41Sopenharmony_ci c.write('\n_renegotiated\n'); 2901cb0ef41Sopenharmony_ci handleConnection(c); 2911cb0ef41Sopenharmony_ci }); 2921cb0ef41Sopenharmony_ci }, 200); 2931cb0ef41Sopenharmony_ci return; 2941cb0ef41Sopenharmony_ci } 2951cb0ef41Sopenharmony_ci 2961cb0ef41Sopenharmony_ci if (c.authorized) { 2971cb0ef41Sopenharmony_ci console.error(`${prefix}- authed connection: ${ 2981cb0ef41Sopenharmony_ci c.getPeerCertificate().subject.CN}`); 2991cb0ef41Sopenharmony_ci c.write('\n_authed\n'); 3001cb0ef41Sopenharmony_ci } else { 3011cb0ef41Sopenharmony_ci console.error(`${prefix}- unauthed connection: %s`, c.authorizationError); 3021cb0ef41Sopenharmony_ci c.write('\n_unauthed\n'); 3031cb0ef41Sopenharmony_ci } 3041cb0ef41Sopenharmony_ci }); 3051cb0ef41Sopenharmony_ci 3061cb0ef41Sopenharmony_ci function runNextClient(clientIndex) { 3071cb0ef41Sopenharmony_ci const options = tcase.clients[clientIndex]; 3081cb0ef41Sopenharmony_ci if (options) { 3091cb0ef41Sopenharmony_ci runClient(`${prefix}${clientIndex} `, port, options, function() { 3101cb0ef41Sopenharmony_ci runNextClient(clientIndex + 1); 3111cb0ef41Sopenharmony_ci }); 3121cb0ef41Sopenharmony_ci } else { 3131cb0ef41Sopenharmony_ci server.close(); 3141cb0ef41Sopenharmony_ci successfulTests++; 3151cb0ef41Sopenharmony_ci runTest(0, nextTest++); 3161cb0ef41Sopenharmony_ci } 3171cb0ef41Sopenharmony_ci } 3181cb0ef41Sopenharmony_ci 3191cb0ef41Sopenharmony_ci server.listen(port, function() { 3201cb0ef41Sopenharmony_ci port = server.address().port; 3211cb0ef41Sopenharmony_ci if (tcase.debug) { 3221cb0ef41Sopenharmony_ci console.error(`${prefix}TLS server running on port ${port}`); 3231cb0ef41Sopenharmony_ci } else if (tcase.renegotiate) { 3241cb0ef41Sopenharmony_ci runNextClient(0); 3251cb0ef41Sopenharmony_ci } else { 3261cb0ef41Sopenharmony_ci let clientsCompleted = 0; 3271cb0ef41Sopenharmony_ci for (let i = 0; i < tcase.clients.length; i++) { 3281cb0ef41Sopenharmony_ci runClient(`${prefix}${i} `, port, tcase.clients[i], function() { 3291cb0ef41Sopenharmony_ci clientsCompleted++; 3301cb0ef41Sopenharmony_ci if (clientsCompleted === tcase.clients.length) { 3311cb0ef41Sopenharmony_ci server.close(); 3321cb0ef41Sopenharmony_ci successfulTests++; 3331cb0ef41Sopenharmony_ci runTest(0, nextTest++); 3341cb0ef41Sopenharmony_ci } 3351cb0ef41Sopenharmony_ci }); 3361cb0ef41Sopenharmony_ci } 3371cb0ef41Sopenharmony_ci } 3381cb0ef41Sopenharmony_ci }); 3391cb0ef41Sopenharmony_ci} 3401cb0ef41Sopenharmony_ci 3411cb0ef41Sopenharmony_ci 3421cb0ef41Sopenharmony_cilet nextTest = 0; 3431cb0ef41Sopenharmony_cirunTest(0, nextTest++); 3441cb0ef41Sopenharmony_ci 3451cb0ef41Sopenharmony_ci 3461cb0ef41Sopenharmony_ciprocess.on('exit', function() { 3471cb0ef41Sopenharmony_ci assert.strictEqual(successfulTests, testCases.length); 3481cb0ef41Sopenharmony_ci}); 349