Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add jest-serializer module #5609

Merged
merged 3 commits into from
Feb 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/jest-cli/src/search_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import type {Glob, GlobalConfig, Path} from 'types/Config';
import type {Test} from 'types/TestRunner';
import type {ChangedFilesPromise} from 'types/ChangedFiles';

import fs from 'fs';
import path from 'path';
import micromatch from 'micromatch';
import DependencyResolver from 'jest-resolve-dependencies';
Expand Down
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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some Flow maybe?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, pardon this is a test (so it's not necessary to flow it, but I like to mix these)

*/

'use strict';

import prettyFormat from 'pretty-format';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pretty-format should go to devDependencies now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right! good catch!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, actually no 😅. There's no way you can develop jest without having pretty-format (yarn complained when trying to add it, and it ended up adding it to the root package.json instead of the child one).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, I thought about this because it's going to be a standalone package, but then again, it's still inside a workspace monorepo. 👍


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(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice workaround on different different contexts ^^

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☺️

prettyFormat(obj),
);
});

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

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