aboutsummaryrefslogtreecommitdiff
path: root/tools/reproduceriter.py
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2012-09-06 18:23:41 -0700
committerAlon Zakai <alonzakai@gmail.com>2012-09-06 18:23:41 -0700
commitce5523f896495b571e321970be68a8da4b1059fb (patch)
tree608e84d59c1dcb17654ecbb2d74bd640979d4e96 /tools/reproduceriter.py
parentd5f8e78bfaad5e08a229d8cd4a75b54c050b7463 (diff)
initial work on reproducer-rewriter
Diffstat (limited to 'tools/reproduceriter.py')
-rw-r--r--tools/reproduceriter.py165
1 files changed, 165 insertions, 0 deletions
diff --git a/tools/reproduceriter.py b/tools/reproduceriter.py
new file mode 100644
index 00000000..df5b7df7
--- /dev/null
+++ b/tools/reproduceriter.py
@@ -0,0 +1,165 @@
+'''
+
+* This is a work in progress *
+
+Reproducer Rewriter
+===================
+
+Processes a project and rewrites it so as to generate deterministic,
+reproducible automatic results. For example, you can run this on a
+game, and then when the game is run it will record user input and
+sources of nondeterminism like Math.random(). You can then run
+that recording as a benchmark or as a demo, it should give nearly
+identical results every time it is run to the data that was
+recorded.
+
+Usage:
+
+1. Run this script as
+
+ reproduceriter.py IN_DIR OUT_DIR FIRST_JS START_COMMAND
+
+ IN_DIR should be the project directory, and OUT_DIR will be
+ created with the instrumented code (OUT_DIR should not
+ exist). FIRST_JS should be a path (relative to IN_DIR) to
+ the first JavaScript file loaded by the project (this tool
+ will add code to that).
+
+2. Run the instrumented project in OUR_DIR and interact with
+ the program. When you are done recording, open the web
+ console and run
+
+ Recorder.finish();
+
+ This will write out the recorded data into the current tab.
+ Save it as
+
+ repro.data
+
+ in OUT_DIR.
+
+3. To re-play the recorded data, run the instrumented build
+ with
+
+ &reproduce=repro.data
+
+Notes:
+
+ * When we start to replay events, the assumption is that
+ there is nothing asynchronous that affects execution. So
+ asynchronous loading of files should have already
+ completed.
+
+ TODO: start running recorded events with some trigger, for example the fullscreen button in BananaBread
+'''
+
+import os, sys, shutil
+
+assert len(sys.argv) == 4, 'Usage: reproduceriter.py IN_DIR OUT_DIR'
+
+# Process input args
+
+in_dir = sys.argv[1]
+out_dir = sys.argv[2]
+first_js = sys.argv[3]
+
+assert not os.path.exists(out_dir), 'OUT_DIR must not exist'
+assert os.path.exists(os.path.join(in_dir, first_js))
+
+# Copy project
+
+shutil.copytree(in_dir, out_dir)
+
+# Add our boilerplate
+
+open(os.path.join(out_dir, first_js), 'w').write('''
+var Recorder = (function() {
+ var recorder;
+ var init = '&reproduce=';
+ var initLocation = window.location.search.indexOf(init);
+ var replaying = initLocation >= 0
+ if (replaying) {
+ // load recording
+ var dataPath = window.location.search.substring(initLocation);
+ var baseURL = window.location.toString().replace('://', 'cheez999').split('?')[0].split('/')[0].replace('cheez999', '://');
+ if (baseURL[baseURL.length-1] != '/') baseURL += '/';
+ var request = new XMLHttpRequest();
+ request.open('GET', baseURL + dataPath, false);
+ request.send();
+ recorder = JSON.parse(request.responseText);
+ // prepare to replay
+ // Math.random
+ var warned = false;
+ Math.random = function() {
+ if (recorder.randoms.length > 0) {
+ return recorder.randoms.shift();
+ } else {
+ if (!warned) {
+ console.log('warning: consuming too many values!')
+ warned = true;
+ }
+ return Math.random();
+ }
+ };
+ // Date.now, performance.now
+ var warned = false;
+ Date.now = function() {
+ if (recorder.dnows.length > 0) {
+ return recorder.dnows.shift();
+ } else {
+ if (!warned) {
+ console.log('warning: consuming too many values!')
+ warned = true;
+ }
+ return Date.now();
+ }
+ };
+ var warned = false;
+ performance.now = function() {
+ if (recorder.pnows.length > 0) {
+ return recorder.pnows.shift();
+ } else {
+ if (!warned) {
+ console.log('warning: consuming too many values!')
+ warned = true;
+ }
+ return performance.now();
+ }
+ };
+ } else {
+ // prepare to record
+ recorder = {};
+ // Math.random
+ recorder.randoms = [];
+ var random = Math.random()
+ Math.random = function() {
+ var ret = random();
+ recorder.randoms.push(ret);
+ return ret;
+ };
+ // Date.now, performance.now
+ recorder.dnows = [];
+ var dnow = Date.now();
+ Date.now = function() {
+ var ret = dnow();
+ recorder.dnows.push(ret);
+ return ret;
+ };
+ recorder.pnows = [];
+ var pnow = performance.now();
+ performance.now = function() {
+ var ret = pnow();
+ recorder.pnows.push(ret);
+ return ret;
+ };
+ // finish
+ recorder.finish = function() {
+ document.write(JSON.stringify(recorder));
+ throw 'all done, remember to save!';
+ };
+ }
+ return recorder;
+})();
+''' + open(os.path.join(in_dir, first_js)).read()
+)
+