From a876c3873809b9eddbdc39f260c5338b99ec4cc2 Mon Sep 17 00:00:00 2001 From: Eugene Ostroukhov Date: Thu, 1 Sep 2016 16:25:02 -0700 Subject: [PATCH] inspector: introduce a smoke test This test executes a simple debug session over the inspector protocol. --- Makefile | 7 +- src/inspector_agent.cc | 3 + src/inspector_socket.cc | 2 +- test/README.md | 9 + test/fixtures/loop.js | 10 + test/inspector/inspector-helper.js | 424 +++++++++++++++++++++++++++++ test/inspector/test-inspector.js | 167 ++++++++++++ test/inspector/testcfg.py | 6 + tools/test.py | 1 + vcbuild.bat | 7 +- 10 files changed, 630 insertions(+), 6 deletions(-) create mode 100644 test/fixtures/loop.js create mode 100644 test/inspector/inspector-helper.js create mode 100644 test/inspector/test-inspector.js create mode 100644 test/inspector/testcfg.py diff --git a/Makefile b/Makefile index 96e80e184d6314..81700ed50f0d1d 100644 --- a/Makefile +++ b/Makefile @@ -119,7 +119,7 @@ test: all $(MAKE) build-addons $(MAKE) cctest $(PYTHON) tools/test.py --mode=release -J \ - addons doctool known_issues message pseudo-tty parallel sequential + addons doctool inspector known_issues message pseudo-tty parallel sequential $(MAKE) lint test-parallel: all @@ -193,7 +193,7 @@ test-all-valgrind: test-build $(PYTHON) tools/test.py --mode=debug,release --valgrind CI_NATIVE_SUITES := addons -CI_JS_SUITES := doctool known_issues message parallel pseudo-tty sequential +CI_JS_SUITES := doctool inspector known_issues message parallel pseudo-tty sequential # Build and test addons without building anything else test-ci-native: | test/addons/.buildstamp @@ -233,6 +233,9 @@ test-internet: all test-debugger: all $(PYTHON) tools/test.py debugger +test-inspector: all + $(PYTHON) tools/test.py inspector + test-known-issues: all $(PYTHON) tools/test.py known_issues diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index d1c12d6e5a99f6..9c1dc6b8995c8e 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -48,6 +48,7 @@ void PrintDebuggerReadyMessage(int port) { "@%s/inspector.html?" "experiments=true&v8only=true&ws=localhost:%d/node\n", port, DEVTOOLS_HASH, port); + fflush(stderr); } bool AcceptsConnection(inspector_socket_t* socket, const std::string& path) { @@ -525,6 +526,7 @@ bool AgentImpl::IsStarted() { void AgentImpl::WaitForDisconnect() { shutting_down_ = true; fprintf(stderr, "Waiting for the debugger to disconnect...\n"); + fflush(stderr); inspector_->runMessageLoopOnPause(0); } @@ -620,6 +622,7 @@ bool AgentImpl::OnInspectorHandshakeIO(inspector_socket_t* socket, return false; default: UNREACHABLE(); + return false; } } diff --git a/src/inspector_socket.cc b/src/inspector_socket.cc index ddf9e865229d4a..5160843ff2dfe6 100644 --- a/src/inspector_socket.cc +++ b/src/inspector_socket.cc @@ -33,7 +33,7 @@ static void dump_hex(const char* buf, size_t len) { while (ptr < end) { cptr = ptr; for (i = 0; i < 16 && ptr < end; i++) { - printf("%2.2X ", *(ptr++)); + printf("%2.2X ", static_cast(*(ptr++))); } for (i = 72 - (i * 4); i > 0; i--) { printf(" "); diff --git a/test/README.md b/test/README.md index 73512413c02834..a32266f085d71e 100644 --- a/test/README.md +++ b/test/README.md @@ -54,6 +54,15 @@ Tests for garbage collection related functionality. |:----------:| | No | + +### inspector + +Tests for the V8 inspector integration. + +| Runs on CI | +|:----------:| +| Yes | + ### internet Tests that make real outbound connections (mainly networking related modules). diff --git a/test/fixtures/loop.js b/test/fixtures/loop.js new file mode 100644 index 00000000000000..e91b831d01b816 --- /dev/null +++ b/test/fixtures/loop.js @@ -0,0 +1,10 @@ +var t = 1; +var k = 1; +console.log('A message', 5); +while (t > 0) { + if (t++ === 1000) { + t = 0; + console.log('Outputed message #' + k++); + } +} +process.exit(55); diff --git a/test/inspector/inspector-helper.js b/test/inspector/inspector-helper.js new file mode 100644 index 00000000000000..865ddeb3b5fefa --- /dev/null +++ b/test/inspector/inspector-helper.js @@ -0,0 +1,424 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const http = require('http'); +const path = require('path'); +const spawn = require('child_process').spawn; + +const TIMEOUT = 15 * 1000; + +const mainScript = path.join(common.fixturesDir, 'loop.js'); + +function send(socket, message, id, callback) { + const msg = JSON.parse(JSON.stringify(message)); // Clone! + msg['id'] = id; + const messageBuf = Buffer.from(JSON.stringify(msg)); + + const wsHeaderBuf = Buffer.allocUnsafe(16); + wsHeaderBuf.writeUInt8(0x81, 0); + let byte2 = 0x80; + const bodyLen = messageBuf.length; + let maskOffset = 2; + if (bodyLen < 126) { + byte2 = 0x80 + bodyLen; + } else if (bodyLen < 65536) { + byte2 = 0xFE; + wsHeaderBuf.writeUInt16BE(bodyLen, 2); + maskOffset = 4; + } else { + byte2 = 0xFF; + wsHeaderBuf.writeUInt32BE(bodyLen, 2); + wsHeaderBuf.writeUInt32BE(0, 6); + maskOffset = 10; + } + wsHeaderBuf.writeUInt8(byte2, 1); + wsHeaderBuf.writeUInt32BE(0x01020408, maskOffset); + + for (let i = 0; i < messageBuf.length; i++) + messageBuf[i] = messageBuf[i] ^ (1 << (i % 4)); + socket.write( + Buffer.concat([wsHeaderBuf.slice(0, maskOffset + 4), messageBuf]), + callback); +} + +function parseWSFrame(buffer, handler) { + if (buffer.length < 2) + return 0; + assert.strictEqual(0x81, buffer[0]); + let dataLen = 0x7F & buffer[1]; + let bodyOffset = 2; + if (buffer.length < bodyOffset + dataLen) + return 0; + if (dataLen === 126) { + dataLen = buffer.readUInt16BE(2); + bodyOffset = 4; + } else if (dataLen === 127) { + dataLen = buffer.readUInt32BE(2); + bodyOffset = 10; + } + if (buffer.length < bodyOffset + dataLen) + return 0; + const message = JSON.parse( + buffer.slice(bodyOffset, bodyOffset + dataLen).toString('utf8')); + handler(message); + return bodyOffset + dataLen; +} + +function tearDown(child, err) { + child.kill(); + if (err instanceof Error) { + console.error(err.stack); + process.exit(1); + } +} + +function checkHttpResponse(port, path, callback) { + http.get({port, path}, function(res) { + let response = ''; + res.setEncoding('utf8'); + res. + on('data', (data) => response += data.toString()). + on('end', () => callback(JSON.parse(response))); + }); +} + +function makeBufferingDataCallback(dataCallback) { + let buffer = new Buffer(0); + return (data) => { + const newData = Buffer.concat([buffer, data]); + const str = newData.toString('utf8'); + const lines = str.split('\n'); + if (str.endsWith('\n')) + buffer = new Buffer(0); + else + buffer = Buffer.from(lines.pop(), 'utf8'); + for (var line of lines) + dataCallback(line); + }; +} + +function timeout(message, multiplicator) { + return setTimeout(() => common.fail(message), TIMEOUT * (multiplicator || 1)); +} + +const TestSession = function(socket, harness) { + this.mainScriptPath = harness.mainScriptPath; + this.mainScriptId = null; + + this.harness_ = harness; + this.socket_ = socket; + this.expectClose_ = false; + this.scripts_ = {}; + this.messagefilter_ = null; + this.responseCheckers_ = {}; + this.lastId_ = 0; + this.messages_ = {}; + this.expectedId_ = 1; + this.lastMessageResponseCallback_ = null; + + let buffer = Buffer.from(''); + socket.on('data', (data) => { + buffer = Buffer.concat([buffer, data]); + let consumed; + do { + consumed = parseWSFrame(buffer, this.processMessage_.bind(this)); + if (consumed) + buffer = buffer.slice(consumed); + } while (consumed); + }).on('close', () => assert(this.expectClose_, 'Socket closed prematurely')); +}; + +TestSession.prototype.scriptUrlForId = function(id) { + return this.scripts_[id]; +}; + +TestSession.prototype.processMessage_ = function(message) { + const method = message['method']; + if (method === 'Debugger.scriptParsed') { + const script = message['params']; + const scriptId = script['scriptId']; + const url = script['url']; + this.scripts_[scriptId] = url; + if (url === mainScript) + this.mainScriptId = scriptId; + } + this.messagefilter_ && this.messagefilter_(message); + const id = message['id']; + if (id) { + assert.strictEqual(id, this.expectedId_); + this.expectedId_++; + if (this.responseCheckers_[id]) { + assert(message['result'], JSON.stringify(message) + ' (response to ' + + JSON.stringify(this.messages_[id]) + ')'); + this.responseCheckers_[id](message['result']); + delete this.responseCheckers_[id]; + } + assert(!message['error'], JSON.stringify(message) + ' (replying to ' + + JSON.stringify(this.messages_[id]) + ')'); + delete this.messages_[id]; + if (id === this.lastId_) { + this.lastMessageResponseCallback_ && this.lastMessageResponseCallback_(); + this.lastMessageResponseCallback_ = null; + } + } +}; + +TestSession.prototype.sendAll_ = function(commands, callback) { + if (!commands.length) { + callback(); + } else { + this.lastId_++; + let command = commands[0]; + if (command instanceof Array) { + this.responseCheckers_[this.lastId_] = command[1]; + command = command[0]; + } + if (command instanceof Function) + command = command(); + this.messages_[this.lastId_] = command; + send(this.socket_, command, this.lastId_, + () => this.sendAll_(commands.slice(1), callback)); + } +}; + +TestSession.prototype.sendInspectorCommands = function(commands) { + if (!(commands instanceof Array)) + commands = [commands]; + return this.enqueue((callback) => { + let timeoutId = null; + this.lastMessageResponseCallback_ = () => { + timeoutId && clearTimeout(timeoutId); + callback(); + }; + this.sendAll_(commands, () => { + timeoutId = setTimeout(() => { + let s = ''; + for (const id in this.messages_) { + s += this.messages_[id] + '\n'; + } + common.fail(s.substring(0, s.length - 1)); + }, TIMEOUT); + }); + }); +}; + +TestSession.prototype.createCallbackWithTimeout_ = function(message) { + var promise = new Promise((resolve) => { + this.enqueue((callback) => { + const timeoutId = timeout(message); + resolve(() => { + clearTimeout(timeoutId); + callback(); + }); + }); + }); + return () => promise.then((callback) => callback()); +}; + +TestSession.prototype.expectMessages = function(expects) { + if (!(expects instanceof Array)) expects = [ expects ]; + + const callback = this.createCallbackWithTimeout_( + 'Matching response was not received:\n' + expects[0]); + this.messagefilter_ = (message) => { + if (expects[0](message)) + expects.shift(); + if (!expects.length) { + this.messagefilter_ = null; + callback(); + } + }; + return this; +}; + +TestSession.prototype.expectStderrOutput = function(regexp) { + this.harness_.addStderrFilter( + regexp, + this.createCallbackWithTimeout_('Timed out waiting for ' + regexp)); + return this; +}; + +TestSession.prototype.runNext_ = function() { + if (this.task_) { + setTimeout(() => { + this.task_(() => { + this.task_ = this.task_.next_; + this.runNext_(); + }); + }); + } +}; + +TestSession.prototype.enqueue = function(task) { + if (!this.task_) { + this.task_ = task; + this.runNext_(); + } else { + let t = this.task_; + while (t.next_) + t = t.next_; + t.next_ = task; + } + return this; +}; + +TestSession.prototype.disconnect = function(childDone) { + return this.enqueue((callback) => { + this.expectClose_ = true; + this.harness_.childInstanceDone = + this.harness_.childInstanceDone || childDone; + this.socket_.end(); + callback(); + }); +}; + +const Harness = function(port, childProcess) { + this.port = port; + this.mainScriptPath = mainScript; + this.stderrFilters_ = []; + this.process_ = childProcess; + this.childInstanceDone = false; + this.returnCode_ = null; + this.running_ = true; + + childProcess.stdout.on('data', makeBufferingDataCallback( + (line) => console.log('[out]', line))); + + + childProcess.stderr.on('data', makeBufferingDataCallback((message) => { + const pending = []; + console.log('[err]', message); + for (const filter of this.stderrFilters_) + if (!filter(message)) pending.push(filter); + this.stderrFilters_ = pending; + })); + childProcess.on('close', (code, signal) => { + assert(this.childInstanceDone, 'Child instance died prematurely'); + this.returnCode_ = code; + this.running_ = false; + }); +}; + +Harness.prototype.addStderrFilter = function(regexp, callback) { + this.stderrFilters_.push((message) => { + if (message.match(regexp)) { + callback(); + return true; + } + }); +}; + +Harness.prototype.run_ = function() { + setTimeout(() => { + this.task_(() => { + this.task_ = this.task_.next_; + if (this.task_) + this.run_(); + }); + }); +}; + +Harness.prototype.enqueue_ = function(task) { + if (!this.task_) { + this.task_ = task; + this.run_(); + } else { + let chain = this.task_; + while (chain.next_) + chain = chain.next_; + chain.next_ = task; + } + return this; +}; + +Harness.prototype.testHttpResponse = function(path, check) { + return this.enqueue_((doneCallback) => { + checkHttpResponse(this.port, path, (response) => { + check.call(this, response); + doneCallback(); + }); + }); +}; + +Harness.prototype.runFrontendSession = function(tests) { + return this.enqueue_((callback) => { + http.get({ + port: this.port, + path: '/node', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Version': 13, + 'Sec-WebSocket-Key': 'key==' + } + }).on('upgrade', (message, socket) => { + const session = new TestSession(socket, this); + if (!(tests instanceof Array)) + tests = [tests]; + function enqueue(tests) { + session.enqueue((sessionCb) => { + if (tests.length) { + tests[0](session); + session.enqueue((cb2) => { + enqueue(tests.slice(1)); + cb2(); + }); + } else { + callback(); + } + sessionCb(); + }); + } + enqueue(tests); + }).on('response', () => common.fail('Upgrade was not received')); + }); +}; + +Harness.prototype.expectShutDown = function(errorCode) { + this.enqueue_((callback) => { + if (this.running_) { + const timeoutId = timeout('Have not terminated'); + this.process_.on('exit', (code) => { + clearTimeout(timeoutId); + assert.strictEqual(errorCode, code); + callback(); + }); + } else { + assert.strictEqual(errorCode, this.returnCode_); + callback(); + } + }); +}; + +exports.startNodeForInspectorTest = function(callback) { + const child = spawn(process.execPath, + [ '--inspect', '--debug-brk', mainScript ]); + + const timeoutId = timeout('Child process did not start properly', 4); + + let found = false; + + const dataCallback = makeBufferingDataCallback((text) => { + clearTimeout(timeoutId); + console.log('[err]', text); + if (found) return; + const match = text.match(/Debugger listening on port (\d+)/); + found = true; + child.stderr.removeListener('data', dataCallback); + assert.ok(match, text); + callback(new Harness(match[1], child)); + }); + + child.stderr.on('data', dataCallback); + + const handler = tearDown.bind(null, child); + + process.on('exit', handler); + process.on('uncaughtException', handler); + process.on('SIGINT', handler); +}; + +exports.mainScriptSource = function() { + return fs.readFileSync(mainScript, 'utf8'); +}; diff --git a/test/inspector/test-inspector.js b/test/inspector/test-inspector.js new file mode 100644 index 00000000000000..2e550f6a02677c --- /dev/null +++ b/test/inspector/test-inspector.js @@ -0,0 +1,167 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const helper = require('./inspector-helper.js'); + +let scopeId; + +function checkListResponse(response) { + assert.strictEqual(1, response.length); + assert.ok(response[0]['devtoolsFrontendUrl']); + assert.strictEqual('ws://localhost:' + this.port + '/node', + response[0]['webSocketDebuggerUrl']); +} + +function expectMainScriptSource(result) { + const expected = helper.mainScriptSource(); + const source = result['scriptSource']; + assert(source && (source.indexOf(expected) >= 0), + 'Script source is wrong: ' + source); +} + +function setupExpectBreakOnLine(line, url, session, scopeIdCallback) { + return function(message) { + if ('Debugger.paused' === message['method']) { + const callFrame = message['params']['callFrames'][0]; + const location = callFrame['location']; + assert.strictEqual(url, session.scriptUrlForId(location['scriptId'])); + assert.strictEqual(line, location['lineNumber']); + scopeIdCallback && + scopeIdCallback(callFrame['scopeChain'][0]['object']['objectId']); + return true; + } + }; +} + +function setupExpectConsoleOutput(type, values) { + if (!(values instanceof Array)) + values = [ values ]; + return function(message) { + if ('Runtime.consoleAPICalled' === message['method']) { + const params = message['params']; + if (params['type'] === type) { + let i = 0; + for (const value of params['args']) { + if (value['value'] !== values[i++]) + return false; + } + return i === values.length; + } + } + }; +} + +function setupExpectScopeValues(expected) { + return function(result) { + for (const actual of result['result']) { + const value = expected[actual['name']]; + if (value) + assert.strictEqual(value, actual['value']['value']); + } + }; +} + +function setupExpectValue(value) { + return function(result) { + assert.strictEqual(value, result['result']['value']); + }; +} + +function testBreakpointOnStart(session) { + console.log('[test]', + 'Verifying debugger stops on start (--debug-brk option)'); + const commands = [ + { 'method': 'Runtime.enable' }, + { 'method': 'Debugger.enable' }, + { 'method': 'Debugger.setPauseOnExceptions', + 'params': {'state': 'none'} }, + { 'method': 'Debugger.setAsyncCallStackDepth', + 'params': {'maxDepth': 0} }, + { 'method': 'Profiler.enable' }, + { 'method': 'Profiler.setSamplingInterval', + 'params': {'interval': 100} }, + { 'method': 'Debugger.setBlackboxPatterns', + 'params': {'patterns': []} }, + { 'method': 'Runtime.runIfWaitingForDebugger' } + ]; + + session. + sendInspectorCommands(commands). + expectMessages(setupExpectBreakOnLine(0, session.mainScriptPath, session)); +} + +function testSetBreakpointAndResume(session) { + console.log('[test]', 'Setting a breakpoint and verifying it is hit'); + const commands = [ + { 'method': 'Debugger.setBreakpointByUrl', + 'params': { 'lineNumber': 5, + 'url': session.mainScriptPath, + 'columnNumber': 0, + 'condition': '' + } + }, + { 'method': 'Debugger.resume'}, + [ { 'method': 'Debugger.getScriptSource', + 'params': { 'scriptId': session.mainScriptId } }, + expectMainScriptSource ], + ]; + session. + sendInspectorCommands(commands). + expectMessages([ + setupExpectConsoleOutput('log', ['A message', 5]), + setupExpectBreakOnLine(5, session.mainScriptPath, + session, (id) => scopeId = id), + ]); +} + +function testInspectScope(session) { + console.log('[test]', 'Verify we can read current application state'); + session.sendInspectorCommands([ + [ + { + 'method': 'Runtime.getProperties', + 'params': { + 'objectId': scopeId, + 'ownProperties': false, + 'accessorPropertiesOnly': false, + 'generatePreview': true + } + }, setupExpectScopeValues({t: 1001, k: 1}) + ], + [ + { + 'method': 'Debugger.evaluateOnCallFrame', 'params': { + 'callFrameId': '{\"ordinal\":0,\"injectedScriptId\":1}', + 'expression': 'k + t', + 'objectGroup': 'console', + 'includeCommandLineAPI': true, + 'silent': false, + 'returnByValue': false, + 'generatePreview': true + } + }, setupExpectValue(1002) + ], + ]); +} + +function testWaitsForFrontendDisconnect(session, harness) { + console.log('[test]', 'Verify node waits for the frontend to disconnect'); + session.sendInspectorCommands({ 'method': 'Debugger.resume'}). + expectStderrOutput('Waiting for the debugger to disconnect...'). + disconnect(true); +} + +function runTests(harness) { + harness + .testHttpResponse('/json', checkListResponse) + .testHttpResponse('/json/list', checkListResponse) + .testHttpResponse('/json/version', assert.ok) + .runFrontendSession([ + testBreakpointOnStart, + testSetBreakpointAndResume, + testInspectScope, + testWaitsForFrontendDisconnect + ]).expectShutDown(55); +} + +helper.startNodeForInspectorTest(runTests); diff --git a/test/inspector/testcfg.py b/test/inspector/testcfg.py new file mode 100644 index 00000000000000..93182b6abed515 --- /dev/null +++ b/test/inspector/testcfg.py @@ -0,0 +1,6 @@ +import sys, os +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +import testpy + +def GetConfiguration(context, root): + return testpy.SimpleTestConfiguration(context, root, 'inspector') diff --git a/tools/test.py b/tools/test.py index 2613ad59d7dbc3..b543010990c364 100755 --- a/tools/test.py +++ b/tools/test.py @@ -1465,6 +1465,7 @@ def ExpandCommand(args): 'gc', 'debugger', 'doctool', + 'inspector', ] diff --git a/vcbuild.bat b/vcbuild.bat index 31fe8670ab4268..fafa5491d0b570 100644 --- a/vcbuild.bat +++ b/vcbuild.bat @@ -57,14 +57,15 @@ if /i "%1"=="noetw" set noetw=1&goto arg-ok if /i "%1"=="noperfctr" set noperfctr=1&goto arg-ok if /i "%1"=="licensertf" set licensertf=1&goto arg-ok if /i "%1"=="test" set test_args=%test_args% addons doctool known_issues message parallel sequential -J&set jslint=1&set build_addons=1&goto arg-ok -if /i "%1"=="test-ci" set test_args=%test_args% %test_ci_args% -p tap --logfile test.tap addons doctool known_issues message sequential parallel&set build_addons=1&goto arg-ok +if /i "%1"=="test-ci" set test_args=%test_args% %test_ci_args% -p tap --logfile test.tap addons doctool inspector known_issues message sequential parallel&set build_addons=1&goto arg-ok if /i "%1"=="test-addons" set test_args=%test_args% addons&set build_addons=1&goto arg-ok if /i "%1"=="test-simple" set test_args=%test_args% sequential parallel -J&goto arg-ok if /i "%1"=="test-message" set test_args=%test_args% message&goto arg-ok if /i "%1"=="test-gc" set test_args=%test_args% gc&set buildnodeweak=1&goto arg-ok +if /i "%1"=="test-inspector" set test_args=%test_args% inspector&goto arg-ok if /i "%1"=="test-internet" set test_args=%test_args% internet&goto arg-ok if /i "%1"=="test-pummel" set test_args=%test_args% pummel&goto arg-ok -if /i "%1"=="test-all" set test_args=%test_args% sequential parallel message gc internet pummel&set buildnodeweak=1&set jslint=1&goto arg-ok +if /i "%1"=="test-all" set test_args=%test_args% sequential parallel message gc inspector internet pummel&set buildnodeweak=1&set jslint=1&goto arg-ok if /i "%1"=="test-known-issues" set test_args=%test_args% known_issues&goto arg-ok if /i "%1"=="jslint" set jslint=1&goto arg-ok if /i "%1"=="jslint-ci" set jslint_ci=1&goto arg-ok @@ -387,7 +388,7 @@ echo Failed to create vc project files. goto exit :help -echo vcbuild.bat [debug/release] [msi] [test-all/test-uv/test-internet/test-pummel/test-simple/test-message] [clean] [noprojgen] [small-icu/full-icu/without-intl] [nobuild] [nosign] [x86/x64] [vc2013/vc2015] [download-all] [enable-vtune] +echo vcbuild.bat [debug/release] [msi] [test-all/test-uv/test-inspector/test-internet/test-pummel/test-simple/test-message] [clean] [noprojgen] [small-icu/full-icu/without-intl] [nobuild] [nosign] [x86/x64] [vc2013/vc2015] [download-all] [enable-vtune] echo Examples: echo vcbuild.bat : builds release build echo vcbuild.bat debug : builds debug build