Skip to content

Commit

Permalink
Add jest-serializer module (#5609)
Browse files Browse the repository at this point in the history
  • Loading branch information
mjesun authored Feb 19, 2018
1 parent bea889c commit 3a2854a
Show file tree
Hide file tree
Showing 7 changed files with 335 additions and 33 deletions.
1 change: 1 addition & 0 deletions packages/jest-haste-map/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"fb-watchman": "^2.0.0",
"graceful-fs": "^4.1.11",
"jest-docblock": "^22.2.2",
"jest-serializer": "^22.3.0",
"jest-worker": "^22.2.2",
"micromatch": "^2.3.11",
"sane": "^2.0.0"
Expand Down
42 changes: 9 additions & 33 deletions packages/jest-haste-map/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {version as VERSION} from '../package.json';
import {worker} from './worker';
import crypto from 'crypto';
import EventEmitter from 'events';
import fs from 'graceful-fs';
import getMockName from './get_mock_name';
import getPlatformExtension from './lib/get_platform_extension';
// eslint-disable-next-line import/no-duplicates
Expand All @@ -25,7 +24,7 @@ import normalizePathSep from './lib/normalize_path_sep';
import os from 'os';
import path from 'path';
import sane from 'sane';
import v8 from 'v8';
import serializer from 'jest-serializer';
// eslint-disable-next-line import/default
import watchmanCrawl from './crawlers/watchman';
import WatchmanWatcher from './lib/watchman_watcher';
Expand Down Expand Up @@ -290,21 +289,14 @@ class HasteMap extends EventEmitter {
* 1. read data from the cache or create an empty structure.
*/
read(): InternalHasteMap {
if (v8.deserialize) {
// This may throw. `_buildFileMap` will catch it and create a new map.
const {version, hasteMap} = v8.deserialize(
fs.readFileSync(this._cachePath),
);
if (version !== process.versions.v8) {
throw new Error('jest-haste-map: v8 versions do not match.');
}
return removePrototypes(hasteMap);
} else {
const hasteMap = (JSON.parse(
fs.readFileSync(this._cachePath, 'utf8'),
): InternalHasteMap);
return removePrototypes(hasteMap);
// This may throw. `_buildFileMap` will catch it and create a new map.
const hasteMap: InternalHasteMap = serializer.readFileSync(this._cachePath);

for (const key in hasteMap) {
Object.setPrototypeOf(hasteMap[key], null);
}

return hasteMap;
}

readModuleMap(): ModuleMap {
Expand Down Expand Up @@ -534,17 +526,7 @@ class HasteMap extends EventEmitter {
* 4. serialize the new `HasteMap` in a cache file.
*/
_persist(hasteMap: InternalHasteMap) {
if (v8.serialize) {
fs.writeFileSync(
this._cachePath,
v8.serialize({
hasteMap,
version: process.versions.v8,
}),
);
} else {
fs.writeFileSync(this._cachePath, JSON.stringify(hasteMap), 'utf8');
}
serializer.writeFileSync(this._cachePath, hasteMap);
}

/**
Expand Down Expand Up @@ -913,12 +895,6 @@ class HasteMap extends EventEmitter {
}

const copy = object => Object.assign(Object.create(null), object);
const removePrototypes = object => {
for (const key in object) {
Object.setPrototypeOf(object[key], null);
}
return object;
};

HasteMap.H = H;
HasteMap.ModuleMap = HasteModuleMap;
Expand Down
3 changes: 3 additions & 0 deletions packages/jest-serializer/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**/__mocks__/**
**/__tests__/**
src
54 changes: 54 additions & 0 deletions packages/jest-serializer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# jest-serializer

Module for serializing and deserializing object into memory and disk. By
default, the `v8` implementations are used, but if not present, it defaults to
`JSON` implementation. Both serializers have the advantage of being able to
serialize `Map`, `Set`, `undefined`, `NaN`, etc, although the JSON one does it
through a replacer/reviver.

## Install

```sh
$ yarn add jest-serializer
```

## API

Three kinds of API groups are exposed:

### In-memory serialization: `serialize` and `deserialize`

This set of functions take or return a `Buffer`. All the process happens in
memory. This is useful when willing to transfer over HTTP, TCP or via UNIX
pipes.

```javascript
import {serialize, deserialize} from 'jest-serializer';

const myObject = {
foo: 'bar',
baz: [0, true, '2', [], {}],
};

const buffer = serialize(myObject);
const myCopyObject = deserialize(buffer);
```

### Synchronous persistent filesystem: `readFileSync` and `writeFileSync`

This set of functions allow to send to disk a serialization result and retrieve
it back, in a synchronous way. It mimics the `fs` API so it looks familiar.

```javascript
import {readFileSync, writeFileSync} from 'jest-serializer';

const myObject = {
foo: 'bar',
baz: [0, true, '2', [], {}],
};

const myFile = '/tmp/obj';

writeFileSync(myFile, myObject);
const myCopyObject = readFileSync(myFile);
```
10 changes: 10 additions & 0 deletions packages/jest-serializer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "jest-serializer",
"version": "22.3.0",
"repository": {
"type": "git",
"url": "https://github.com/facebook/jest.git"
},
"license": "MIT",
"main": "build/index.js"
}
93 changes: 93 additions & 0 deletions packages/jest-serializer/src/__tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Copyright (c) 2018-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

import prettyFormat from 'pretty-format';

import fs from 'fs';
import os from 'os';
import path from 'path';
import v8 from 'v8';

import serializer from '..';

const v8s = [
{
deserialize: v8.deserialize,
serialize: v8.serialize,
},
{
deserialize: undefined,
serialize: undefined,
},
];

const objs = [
3,
null,
[0, true, '2', [3.14, {}, null]],
{key1: 'foo', key2: 'bar', key3: {array: [null, {}]}},
{minusInf: -Infinity, nan: NaN, plusInf: +Infinity},
{date: new Date(1234567890), re: /foo/gi},
{map: new Map([[NaN, 4], [undefined, 'm']]), set: new Set([undefined, NaN])},
{buf: Buffer.from([0, 255, 127])},
];

const file = path.join(os.tmpdir(), '__jest-serialize-test__');

afterEach(() => {
try {
fs.unlinkSync(file);
} catch (err) {
// Do nothing if file does not exist.
}
});

// We execute the same suite of tests over multiple objects ("objs") and over
// multiple mocks of the V8 object ("v8s") so that we verify that all possible
// encodings and cases work.
v8s.forEach((mockV8, i) => {
describe('Using V8 implementation ' + i, () => {
beforeEach(() => {
v8.serialize = mockV8.serialize;
v8.deserialize = mockV8.deserialize;
});

it('throws the error with an invalid serialization', () => {
// No chance this is a valid serialization, neither in JSON nor V8.
const invalidBuffer = Buffer.from([0, 85, 170, 255]);

fs.writeFileSync(file, invalidBuffer);

expect(() => serializer.deserialize(invalidBuffer)).toThrow();
expect(() => serializer.readFileSync(file)).toThrow();
});

objs.forEach((obj, i) => {
describe('Object ' + i, () => {
it('serializes/deserializes in memory', () => {
const buf = serializer.serialize(obj);

expect(buf).toBeInstanceOf(Buffer);

expect(prettyFormat(serializer.deserialize(buf))).toEqual(
prettyFormat(obj),
);
});

it('serializes/deserializes in disk', () => {
serializer.writeFileSync(file, obj);

expect(prettyFormat(serializer.readFileSync(file))).toEqual(
prettyFormat(obj),
);
});
});
});
});
});
Loading

0 comments on commit 3a2854a

Please sign in to comment.