From a3a1c50b4eb8ccbde01242c59e7ec40e26514a6c Mon Sep 17 00:00:00 2001 From: David Humphrey Date: Tue, 3 Dec 2013 15:14:20 -0500 Subject: [PATCH] Add Zlib adapter, generalize adapter tests --- lib/zlib.js | 40 ++++++++++++ src/adapters/adapters.js | 8 ++- src/adapters/crypto.js | 41 ++++++++++-- src/adapters/zlib.js | 63 +++++++++++++++++++ ...rypto.spec.js => adapters.general.spec.js} | 52 ++++++++++----- tests/spec/adapters/adapters.spec.js | 8 +++ tests/test-manifest.js | 2 +- 7 files changed, 194 insertions(+), 20 deletions(-) create mode 100644 lib/zlib.js create mode 100644 src/adapters/zlib.js rename tests/spec/adapters/{adapters.crypto.spec.js => adapters.general.spec.js} (76%) diff --git a/lib/zlib.js b/lib/zlib.js new file mode 100644 index 0000000..34f0e8f --- /dev/null +++ b/lib/zlib.js @@ -0,0 +1,40 @@ +/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';function l(d){throw d;}var u=void 0,x=!0,aa=this;function z(d,a){var c=d.split("."),f=aa;!(c[0]in f)&&f.execScript&&f.execScript("var "+c[0]);for(var b;c.length&&(b=c.shift());)!c.length&&a!==u?f[b]=a:f=f[b]?f[b]:f[b]={}};var E="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array;function G(d,a){this.index="number"===typeof a?a:0;this.i=0;this.buffer=d instanceof(E?Uint8Array:Array)?d:new (E?Uint8Array:Array)(32768);2*this.buffer.length<=this.index&&l(Error("invalid index"));this.buffer.length<=this.index&&this.f()}G.prototype.f=function(){var d=this.buffer,a,c=d.length,f=new (E?Uint8Array:Array)(c<<1);if(E)f.set(d);else for(a=0;a>>8&255]<<16|N[d>>>16&255]<<8|N[d>>>24&255])>>32-a:N[d]>>8-a);if(8>a+e)g=g<>a-h-1&1,8===++e&&(e=0,f[b++]=N[g],g=0,b===f.length&&(f=this.f()));f[b]=g;this.buffer=f;this.i=e;this.index=b};G.prototype.finish=function(){var d=this.buffer,a=this.index,c;0O;++O){for(var P=O,Q=P,ga=7,P=P>>>1;P;P>>>=1)Q<<=1,Q|=P&1,--ga;fa[O]=(Q<>>0}var N=fa;function ha(d){this.buffer=new (E?Uint16Array:Array)(2*d);this.length=0}ha.prototype.getParent=function(d){return 2*((d-2)/4|0)};ha.prototype.push=function(d,a){var c,f,b=this.buffer,e;c=this.length;b[this.length++]=a;for(b[this.length++]=d;0b[f])e=b[c],b[c]=b[f],b[f]=e,e=b[c+1],b[c+1]=b[f+1],b[f+1]=e,c=f;else break;return this.length}; +ha.prototype.pop=function(){var d,a,c=this.buffer,f,b,e;a=c[0];d=c[1];this.length-=2;c[0]=c[this.length];c[1]=c[this.length+1];for(e=0;;){b=2*e+2;if(b>=this.length)break;b+2c[b]&&(b+=2);if(c[b]>c[e])f=c[e],c[e]=c[b],c[b]=f,f=c[e+1],c[e+1]=c[b+1],c[b+1]=f;else break;e=b}return{index:d,value:a,length:this.length}};function R(d){var a=d.length,c=0,f=Number.POSITIVE_INFINITY,b,e,g,h,k,n,q,r,p;for(r=0;rc&&(c=d[r]),d[r]>=1;for(p=n;pS;S++)switch(x){case 143>=S:oa.push([S+48,8]);break;case 255>=S:oa.push([S-144+400,9]);break;case 279>=S:oa.push([S-256+0,7]);break;case 287>=S:oa.push([S-280+192,8]);break;default:l("invalid literal: "+S)} +ia.prototype.j=function(){var d,a,c,f,b=this.input;switch(this.h){case 0:c=0;for(f=b.length;c>>8&255;p[m++]=n&255;p[m++]=n>>>8&255;if(E)p.set(e,m),m+=e.length,p=p.subarray(0,m);else{q=0;for(r=e.length;qv)for(;0< +v--;)H[F++]=0,K[0]++;else for(;0v?v:138,C>v-3&&C=C?(H[F++]=17,H[F++]=C-3,K[17]++):(H[F++]=18,H[F++]=C-11,K[18]++),v-=C;else if(H[F++]=I[t],K[I[t]]++,v--,3>v)for(;0v?v:6,C>v-3&&CA;A++)ra[A]=ka[gb[A]];for(W=19;4=b:return[265,b-11,1];case 14>=b:return[266,b-13,1];case 16>=b:return[267,b-15,1];case 18>=b:return[268,b-17,1];case 22>=b:return[269,b-19,2];case 26>=b:return[270,b-23,2];case 30>=b:return[271,b-27,2];case 34>=b:return[272, +b-31,2];case 42>=b:return[273,b-35,3];case 50>=b:return[274,b-43,3];case 58>=b:return[275,b-51,3];case 66>=b:return[276,b-59,3];case 82>=b:return[277,b-67,4];case 98>=b:return[278,b-83,4];case 114>=b:return[279,b-99,4];case 130>=b:return[280,b-115,4];case 162>=b:return[281,b-131,5];case 194>=b:return[282,b-163,5];case 226>=b:return[283,b-195,5];case 257>=b:return[284,b-227,5];case 258===b:return[285,b-258,0];default:l("invalid length: "+b)}}var a=[],c,f;for(c=3;258>=c;c++)f=d(c),a[c]=f[2]<<24|f[1]<< +16|f[0];return a}(),wa=E?new Uint32Array(va):va; +function pa(d,a){function c(b,c){var a=b.G,d=[],e=0,f;f=wa[b.length];d[e++]=f&65535;d[e++]=f>>16&255;d[e++]=f>>24;var g;switch(x){case 1===a:g=[0,a-1,0];break;case 2===a:g=[1,a-2,0];break;case 3===a:g=[2,a-3,0];break;case 4===a:g=[3,a-4,0];break;case 6>=a:g=[4,a-5,1];break;case 8>=a:g=[5,a-7,1];break;case 12>=a:g=[6,a-9,2];break;case 16>=a:g=[7,a-13,2];break;case 24>=a:g=[8,a-17,3];break;case 32>=a:g=[9,a-25,3];break;case 48>=a:g=[10,a-33,4];break;case 64>=a:g=[11,a-49,4];break;case 96>=a:g=[12,a- +65,5];break;case 128>=a:g=[13,a-97,5];break;case 192>=a:g=[14,a-129,6];break;case 256>=a:g=[15,a-193,6];break;case 384>=a:g=[16,a-257,7];break;case 512>=a:g=[17,a-385,7];break;case 768>=a:g=[18,a-513,8];break;case 1024>=a:g=[19,a-769,8];break;case 1536>=a:g=[20,a-1025,9];break;case 2048>=a:g=[21,a-1537,9];break;case 3072>=a:g=[22,a-2049,10];break;case 4096>=a:g=[23,a-3073,10];break;case 6144>=a:g=[24,a-4097,11];break;case 8192>=a:g=[25,a-6145,11];break;case 12288>=a:g=[26,a-8193,12];break;case 16384>= +a:g=[27,a-12289,12];break;case 24576>=a:g=[28,a-16385,13];break;case 32768>=a:g=[29,a-24577,13];break;default:l("invalid distance")}f=g;d[e++]=f[0];d[e++]=f[1];d[e++]=f[2];var h,k;h=0;for(k=d.length;h=e;)w[e++]=0;for(e=0;29>=e;)y[e++]=0}w[256]=1;f=0;for(b=a.length;f=b){r&&c(r,-1);e=0;for(g=b-f;eg&&a+ge&&(b=f,e=g);if(258===g)break}return new ta(e,a-b)} +function qa(d,a){var c=d.length,f=new ha(572),b=new (E?Uint8Array:Array)(c),e,g,h,k,n;if(!E)for(k=0;k2*b[m-1]+e[m]&&(b[m]=2*b[m-1]+e[m]),h[m]=Array(b[m]),k[m]=Array(b[m]);for(p=0;pd[p]?(h[m][s]=w,k[m][s]=a,y+=2):(h[m][s]=d[p],k[m][s]=p,++p);n[m]=0;1===e[m]&&f(m)}return g} +function sa(d){var a=new (E?Uint16Array:Array)(d.length),c=[],f=[],b=0,e,g,h,k;e=0;for(g=d.length;e>>=1}return a};function T(d,a){this.l=[];this.m=32768;this.e=this.g=this.c=this.q=0;this.input=E?new Uint8Array(d):d;this.s=!1;this.n=za;this.B=!1;if(a||!(a={}))a.index&&(this.c=a.index),a.bufferSize&&(this.m=a.bufferSize),a.bufferType&&(this.n=a.bufferType),a.resize&&(this.B=a.resize);switch(this.n){case Aa:this.b=32768;this.a=new (E?Uint8Array:Array)(32768+this.m+258);break;case za:this.b=0;this.a=new (E?Uint8Array:Array)(this.m);this.f=this.J;this.t=this.H;this.o=this.I;break;default:l(Error("invalid inflate mode"))}} +var Aa=0,za=1,Ba={D:Aa,C:za}; +T.prototype.p=function(){for(;!this.s;){var d=Y(this,3);d&1&&(this.s=x);d>>>=1;switch(d){case 0:var a=this.input,c=this.c,f=this.a,b=this.b,e=u,g=u,h=u,k=f.length,n=u;this.e=this.g=0;e=a[c++];e===u&&l(Error("invalid uncompressed block header: LEN (first byte)"));g=e;e=a[c++];e===u&&l(Error("invalid uncompressed block header: LEN (second byte)"));g|=e<<8;e=a[c++];e===u&&l(Error("invalid uncompressed block header: NLEN (first byte)"));h=e;e=a[c++];e===u&&l(Error("invalid uncompressed block header: NLEN (second byte)"));h|= +e<<8;g===~h&&l(Error("invalid uncompressed block header: length verify"));c+g>a.length&&l(Error("input buffer is broken"));switch(this.n){case Aa:for(;b+g>f.length;){n=k-b;g-=n;if(E)f.set(a.subarray(c,c+n),b),b+=n,c+=n;else for(;n--;)f[b++]=a[c++];this.b=b;f=this.f();b=this.b}break;case za:for(;b+g>f.length;)f=this.f({v:2});break;default:l(Error("invalid inflate mode"))}if(E)f.set(a.subarray(c,c+g),b),b+=g,c+=g;else for(;g--;)f[b++]=a[c++];this.c=c;this.b=b;this.a=f;break;case 1:this.o(Ca,Ra);break; +case 2:Sa(this);break;default:l(Error("unknown BTYPE: "+d))}}return this.t()}; +var Ta=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],Ua=E?new Uint16Array(Ta):Ta,Va=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],Wa=E?new Uint16Array(Va):Va,Xa=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],Ya=E?new Uint8Array(Xa):Xa,Za=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],$a=E?new Uint16Array(Za):Za,ab=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10, +10,11,11,12,12,13,13],bb=E?new Uint8Array(ab):ab,cb=new (E?Uint8Array:Array)(288),Z,db;Z=0;for(db=cb.length;Z=Z?8:255>=Z?9:279>=Z?7:8;var Ca=R(cb),eb=new (E?Uint8Array:Array)(30),fb,hb;fb=0;for(hb=eb.length;fb>>a;d.e=f-a;d.c=e;return g} +function ib(d,a){for(var c=d.g,f=d.e,b=d.input,e=d.c,g=a[0],h=a[1],k,n,q;f>>16;d.g=c>>q;d.e=f-q;d.c=e;return n&65535} +function Sa(d){function a(a,b,c){var d,f,e,g;for(g=0;ge)f>=b&&(this.b=f,c=this.f(),f=this.b),c[f++]=e;else{g=e-257;k=Wa[g];0=b&&(this.b=f,c=this.f(),f=this.b);for(;k--;)c[f]=c[f++-h]}for(;8<=this.e;)this.e-=8,this.c--;this.b=f}; +T.prototype.I=function(d,a){var c=this.a,f=this.b;this.u=d;for(var b=c.length,e,g,h,k;256!==(e=ib(this,d));)if(256>e)f>=b&&(c=this.f(),b=c.length),c[f++]=e;else{g=e-257;k=Wa[g];0b&&(c=this.f(),b=c.length);for(;k--;)c[f]=c[f++-h]}for(;8<=this.e;)this.e-=8,this.c--;this.b=f}; +T.prototype.f=function(){var d=new (E?Uint8Array:Array)(this.b-32768),a=this.b-32768,c,f,b=this.a;if(E)d.set(b.subarray(32768,d.length));else{c=0;for(f=d.length;cc;++c)b[c]=b[a+c];this.b=32768;return b}; +T.prototype.J=function(d){var a,c=this.input.length/this.c+1|0,f,b,e,g=this.input,h=this.a;d&&("number"===typeof d.v&&(c=d.v),"number"===typeof d.F&&(c+=d.F));2>c?(f=(g.length-this.c)/this.u[2],e=258*(f/2)|0,b=ea&&(this.a.length=a),d=this.a);return this.buffer=d};function jb(d){if("string"===typeof d){var a=d.split(""),c,f;c=0;for(f=a.length;c>>0;d=a}for(var b=1,e=0,g=d.length,h,k=0;0>>0};function kb(d,a){var c,f;this.input=d;this.c=0;if(a||!(a={}))a.index&&(this.c=a.index),a.verify&&(this.M=a.verify);c=d[this.c++];f=d[this.c++];switch(c&15){case lb:this.method=lb;break;default:l(Error("unsupported compression method"))}0!==((c<<8)+f)%31&&l(Error("invalid fcheck flag:"+((c<<8)+f)%31));f&32&&l(Error("fdict flag is not supported"));this.A=new T(d,{index:this.c,bufferSize:a.bufferSize,bufferType:a.bufferType,resize:a.resize})} +kb.prototype.p=function(){var d=this.input,a,c;a=this.A.p();this.c=this.A.c;this.M&&(c=(d[this.c++]<<24|d[this.c++]<<16|d[this.c++]<<8|d[this.c++])>>>0,c!==jb(a)&&l(Error("invalid adler-32 checksum")));return a};var lb=8;function mb(d,a){this.input=d;this.a=new (E?Uint8Array:Array)(32768);this.h=$.k;var c={},f;if((a||!(a={}))&&"number"===typeof a.compressionType)this.h=a.compressionType;for(f in a)c[f]=a[f];c.outputBuffer=this.a;this.z=new ia(this.input,c)}var $=na; +mb.prototype.j=function(){var d,a,c,f,b,e,g,h=0;g=this.a;d=lb;switch(d){case lb:a=Math.LOG2E*Math.log(32768)-8;break;default:l(Error("invalid compression method"))}c=a<<4|d;g[h++]=c;switch(d){case lb:switch(this.h){case $.NONE:b=0;break;case $.r:b=1;break;case $.k:b=2;break;default:l(Error("unsupported compression type"))}break;default:l(Error("invalid compression method"))}f=b<<6|0;g[h++]=f|31-(256*c+f)%31;e=jb(this.input);this.z.b=h;g=this.z.j();h=g.length;E&&(g=new Uint8Array(g.buffer),g.length<= +h+4&&(this.a=new Uint8Array(g.length+4),this.a.set(g),g=this.a),g=g.subarray(0,h+4));g[h++]=e>>24&255;g[h++]=e>>16&255;g[h++]=e>>8&255;g[h++]=e&255;return g};function nb(d,a){var c,f,b,e;if(Object.keys)c=Object.keys(a);else for(f in c=[],b=0,a)c[b++]=f;b=0;for(e=c.length;b>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + u8[i]=byte; + } + + return u8; + }, + + parse: function (u8arr) { + // Shortcut + var len = u8arr.length; + + // Convert + var words = []; + for (var i = 0; i < len; i++) { + words[i >>> 2] |= (u8arr[i] & 0xff) << (24 - (i % 4) * 8); + } + + return CryptoJS.lib.WordArray.create(words, len); + } + }; function buildCryptoAdapter(encryptionType) { // It is up to the app using this wrapper how the passphrase is acquired, probably by @@ -44,12 +75,14 @@ define(function(require) { function CryptoAdapter(passphrase, provider) { this.provider = provider; this.encrypt = function(plain) { - return CryptoJS[encryptionType].encrypt(plain, passphrase) - .toString(); + return CryptoJS[encryptionType] + .encrypt(plain, passphrase, {format: Uint8ArrayFormatter}) + .toString(); }; this.decrypt = function(encrypted) { - return CryptoJS[encryptionType].decrypt(encrypted, passphrase) - .toString(CryptoJS.enc.Utf8); + return CryptoJS[encryptionType] + .decrypt(encrypted, passphrase, {format: Uint8ArrayFormatter}) + .toString(); //CryptoJS.enc.Utf8); }; } CryptoAdapter.isSupported = function() { diff --git a/src/adapters/zlib.js b/src/adapters/zlib.js new file mode 100644 index 0000000..29d3c87 --- /dev/null +++ b/src/adapters/zlib.js @@ -0,0 +1,63 @@ +define(function(require) { + + // ZLib compression, see + // https://github.com/imaya/zlib.js/blob/master/bin/zlib.min.js + require("zlib"); + + var Inflate = Zlib.Inflate; + function inflate(compressed) { + return (new Inflate(compressed)).decompress(); + } + + var Deflate = Zlib.Deflate; + function deflate(buffer) { + return (new Deflate(buffer)).compress(); + } + + function ZlibContext(context) { + this.context = context; + } + ZlibContext.prototype.clear = function(callback) { + this.context.clear(callback); + }; + ZlibContext.prototype.get = function(key, callback) { + this.context.get(key, function(err, result) { + if(err) { + callback(err); + return; + } + // Deal with result being null + if(result) { + result = inflate(result); + } + callback(null, result); + }); + }; + ZlibContext.prototype.put = function(key, value, callback) { + value = deflate(value); + this.context.put(key, value, callback); + }; + ZlibContext.prototype.delete = function(key, callback) { + this.context.delete(key, callback); + }; + + + function ZlibAdapter(provider, inflate, deflate) { + this.provider = provider; + } + ZlibAdapter.isSupported = function() { + return true; + }; + + ZlibAdapter.prototype.open = function(callback) { + this.provider.open(callback); + }; + ZlibAdapter.prototype.getReadOnlyContext = function() { + return new ZlibContext(this.provider.getReadOnlyContext()); + }; + ZlibAdapter.prototype.getReadWriteContext = function() { + return new ZlibContext(this.provider.getReadWriteContext()); + }; + + return ZlibAdapter; +}); diff --git a/tests/spec/adapters/adapters.crypto.spec.js b/tests/spec/adapters/adapters.general.spec.js similarity index 76% rename from tests/spec/adapters/adapters.crypto.spec.js rename to tests/spec/adapters/adapters.general.spec.js index adbcb85..7e7cf7d 100644 --- a/tests/spec/adapters/adapters.crypto.spec.js +++ b/tests/spec/adapters/adapters.general.spec.js @@ -1,15 +1,23 @@ define(["IDBFS"], function(IDBFS) { - // We reuse the same set of tests for all crypto adapters. - // buildTestsFor() creates a set of tests bound to a crypto + // We reuse the same set of tests for all adapters. + // buildTestsFor() creates a set of tests bound to an // adapter, and uses a Memory() provider internally. - function buildTestsFor(adapterName) { - var passphrase = '' + Date.now(); + function buildTestsFor(adapterName, buildAdapter) { + function encode(str) { + // TextEncoder is either native, or shimmed by IDBFS + return (new TextEncoder("utf-8")).encode(str); + } + + // Make some string + binary buffer versions of things we'll need + var valueStr = "value", valueBuffer = encode(valueStr); + var value1Str = "value1", value1Buffer = encode(value1Str); + var value2Str = "value2", value2Buffer = encode(value2Str); function createProvider() { var memoryProvider = new IDBFS.FileSystem.providers.Memory(); - return new IDBFS.FileSystem.adapters[adapterName](passphrase, memoryProvider); + return buildAdapter(memoryProvider); } describe("IDBFS.FileSystem.adapters." + adapterName, function() { @@ -57,7 +65,7 @@ define(["IDBFS"], function(IDBFS) { _error = err; var context = provider.getReadWriteContext(); - context.put("key", "value", function(err, result) { + context.put("key", valueBuffer, function(err, result) { _error = _error || err; context.get("key", function(err, result) { _error = _error || err; @@ -74,7 +82,7 @@ define(["IDBFS"], function(IDBFS) { runs(function() { expect(_error).toEqual(null); - expect(_result).toEqual("value"); + expect(_result).toEqual(valueBuffer); }); }); @@ -87,7 +95,7 @@ define(["IDBFS"], function(IDBFS) { _error = err; var context = provider.getReadWriteContext(); - context.put("key", "value", function(err, result) { + context.put("key", valueBuffer, function(err, result) { _error = _error || err; context.delete("key", function(err, result) { _error = _error || err; @@ -120,9 +128,9 @@ define(["IDBFS"], function(IDBFS) { _error = err; var context = provider.getReadWriteContext(); - context.put("key1", "value1", function(err, result) { + context.put("key1", value1Buffer, function(err, result) { _error = _error || err; - context.put("key2", "value2", function(err, result) { + context.put("key2", value2Buffer, function(err, result) { _error = _error || err; context.clear(function(err) { @@ -164,7 +172,7 @@ define(["IDBFS"], function(IDBFS) { _error = err; var context = provider.getReadOnlyContext(); - context.put("key1", "value1", function(err, result) { + context.put("key1", value1Buffer, function(err, result) { _error = _error || err; _result = result; @@ -185,8 +193,24 @@ define(["IDBFS"], function(IDBFS) { }); } - buildTestsFor('AES'); - buildTestsFor('TripleDES'); - buildTestsFor('Rabbit'); + + // Encryption + buildTestsFor('AES', function buildAdapter(provider) { + var passphrase = '' + Date.now(); + return new IDBFS.FileSystem.adapters.AES(passphrase, provider); + }); + buildTestsFor('TripleDES', function buildAdapter(provider) { + var passphrase = '' + Date.now(); + return new IDBFS.FileSystem.adapters.TripleDES(passphrase, provider); + }); + buildTestsFor('Rabbit', function buildAdapter(provider) { + var passphrase = '' + Date.now(); + return new IDBFS.FileSystem.adapters.Rabbit(passphrase, provider); + }); + + // Compression + buildTestsFor('Zlib', function buildAdapter(provider) { + return new IDBFS.FileSystem.adapters.Zlib(provider); + }); }); diff --git a/tests/spec/adapters/adapters.spec.js b/tests/spec/adapters/adapters.spec.js index 9d55a59..c3b223a 100644 --- a/tests/spec/adapters/adapters.spec.js +++ b/tests/spec/adapters/adapters.spec.js @@ -19,5 +19,13 @@ define(["IDBFS"], function(IDBFS) { it("has a default Encryption constructor", function() { expect(typeof IDBFS.FileSystem.adapters.Encryption).toEqual('function'); }); + + it("has a Zlib constructor", function() { + expect(typeof IDBFS.FileSystem.adapters.Zlib).toEqual('function'); + }); + + it("has a default Compression constructor", function() { + expect(typeof IDBFS.FileSystem.adapters.Compression).toEqual('function'); + }); }); }); diff --git a/tests/test-manifest.js b/tests/test-manifest.js index 698fe61..0f30a63 100644 --- a/tests/test-manifest.js +++ b/tests/test-manifest.js @@ -36,7 +36,7 @@ define([ // IDBFS.FileSystem.adapters.* "spec/adapters/adapters.spec", - "spec/adapters/adapters.crypto.spec", + "spec/adapters/adapters.general.spec", // Ported node.js tests (filenames match names in https://github.com/joyent/node/tree/master/test) "spec/node-js/simple/test-fs-mkdir",