diff --git a/docs/src/modules/components/Notifications.js b/docs/src/modules/components/Notifications.js index c1ea9c8ba34015..ac422c27765bd2 100644 --- a/docs/src/modules/components/Notifications.js +++ b/docs/src/modules/components/Notifications.js @@ -15,7 +15,6 @@ import ClickAwayListener from '@material-ui/core/ClickAwayListener'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import Divider from '@material-ui/core/Divider'; -import sleep from 'modules/waterfall/sleep'; import { getCookie } from 'docs/src/modules/utils/helpers'; import { ACTION_TYPES } from 'docs/src/modules/constants'; @@ -91,33 +90,33 @@ export default function Notifications() { return undefined; } - (async () => { - await sleep(1500); // Soften the pressure on the main thread. - let newMessages; - try { - const result = await fetch( - 'https://raw.githubusercontent.com/mui-org/material-ui/master/docs/notifications.json', - ); - newMessages = await result.json(); - } catch (err) { - // Swallow the exceptions, e.g. rate limit - } - - if (active) { - const seen = getCookie('lastSeenNotification'); - const lastSeenNotification = seen === '' ? 0 : parseInt(seen, 10); - - dispatch({ - type: ACTION_TYPES.NOTIFICATIONS_CHANGE, - payload: { - messages: newMessages || [], - lastSeen: lastSeenNotification, - }, + // Soften the pressure on the main thread. + const timeout = setTimeout(() => { + fetch('https://raw.githubusercontent.com/mui-org/material-ui/master/docs/notifications.json') + .then((response) => { + return response.json(); + }) + .catch(() => { + // Swallow the exceptions, e.g. rate limit + return []; + }) + .then((newMessages) => { + if (active) { + const seen = getCookie('lastSeenNotification'); + const lastSeenNotification = seen === '' ? 0 : parseInt(seen, 10); + dispatch({ + type: ACTION_TYPES.NOTIFICATIONS_CHANGE, + payload: { + messages: newMessages || [], + lastSeen: lastSeenNotification, + }, + }); + } }); - } - })(); + }, 1500); return () => { + clearTimeout(timeout); active = false; }; }, []); diff --git a/modules/handleKillSignals.js b/modules/handleKillSignals.js deleted file mode 100644 index 98a854d8ba737d..00000000000000 --- a/modules/handleKillSignals.js +++ /dev/null @@ -1,112 +0,0 @@ -const log = require('./log'); - -const SHUTDOWN_TIMEOUT = 10e3; - -let shuttingDown = false; -const teardowns = []; - -function isShuttingDown() { - return shuttingDown; -} - -/** - * shutdown === the termination handler - * Terminate server on receipt of the specified signal. - * @param {string} signal Signal to terminate on. - */ -async function shutdown(signal, origin) { - if (typeof signal === 'string') { - log.info({ - name: 'kill signal', - msg: `Handling signal: ${signal} from ${origin}.`, - }); - } - - // At the first soft kill signal, we try to shutdown the service gracefully. - if ((signal === 'SIGTERM' || signal === 'SIGINT') && !shuttingDown) { - shuttingDown = true; - log.info({ - name: 'kill signal', - msg: `Shutdown server gracefully. ${SHUTDOWN_TIMEOUT}ms before killing it.`, - }); - const timer = setTimeout(() => { - log.fatal({ - name: 'kill signal', - msg: 'Force shutdown', - }); - shutdown(); - }, SHUTDOWN_TIMEOUT); - - // ASC - const teardownsSorted = teardowns.slice().sort((a, b) => a.nice - b.nice); - - // Serial resolution of the teardowns - for (let i = 0; i < teardownsSorted.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - await teardownsSorted[i].callback(); - } - - clearTimeout(timer); - process.exit(0); - return; - } - - process.exit(1); -} - -function addTeardown(teardown) { - teardowns.push(teardown); -} - -function removeTeardown(teardown) { - const index = teardowns.indexOf(teardown); - teardowns.splice(index, 1); -} - -function handleKillSignals() { - // Process on exit and signals. - // https://nodejs.org/api/process.html#process_event_exit - process.on('exit', (code) => { - const msg = `💀 Node stopped with code ${code}`; - - if (code === 0) { - log.info({ - name: 'kill signal', - msg, - }); - } else { - log.fatal({ - name: 'kill signal', - msg, - }); - } - }); - - // Removed 'SIGPIPE' from the list - bugz 852598. - [ - 'SIGHUP', - 'SIGINT', - 'SIGQUIT', - 'SIGILL', - 'SIGTRAP', - 'SIGABRT', - 'SIGBUS', - 'SIGFPE', - 'SIGUSR1', - 'SIGSEGV', - 'SIGUSR2', - 'SIGTERM', - ].forEach((signal) => { - process.on(signal, () => { - shutdown(signal, 'signal'); - }); - }); -} - -module.exports = { - handleKillSignals, - isShuttingDown, - shutdown, - addTeardown, - removeTeardown, -}; diff --git a/modules/log.js b/modules/log.js deleted file mode 100644 index 4eb43e3a231d15..00000000000000 --- a/modules/log.js +++ /dev/null @@ -1,146 +0,0 @@ -/* eslint-disable no-console */ -// https://github.com/trentm/node-bunyan#levels -const logTypes = { - // Detail on regular operation. - info: { level: 3, method: 'log' }, - // A note on something that should probably be looked at by an operator eventually. - warn: { level: 4, method: 'warn' }, - // Fatal for a particular request, but the service/app continues servicing other requests. - // An operator should look at this soon(ish). - error: { level: 5, method: 'error' }, - // The service/app is going to stop or become unusable now. - // An operator should definitely look into this soon. - fatal: { level: 6, method: 'error' }, -}; - -function serializeErr(msg) { - if (!msg.err.stack) { - return msg.err; - } - - return { - ...msg, - err: { - message: msg.err.message, - name: msg.err.name, - }, - }; -} - -function serializeDuration(msg) { - return { - ...msg, - duration: `${msg.duration.toFixed(2)}ms`, - }; -} - -function safeCycles() { - const seen = new Set(); - return function handleKey(key, val) { - if (!val || typeof val !== 'object') { - return val; - } - if (seen.has(val)) { - return '[Circular]'; - } - seen.add(val); - return val; - }; -} - -/** - * A fast JSON.stringify that handles cycles and getter exceptions (when - * safeJsonStringify is installed). - * - * This function attempts to use the regular JSON.stringify for speed, but on - * error (e.g. JSON cycle detection exception) it falls back to safe stringify - * handlers that can deal with cycles and/or getter exceptions. - * - * From: https://github.com/trentm/node-bunyan/blob/c0932196dd6846189ec82623c12d051eee799d4f/lib/bunyan.js#L1208 - */ -function fastAndSafeJsonStringify(object) { - try { - return JSON.stringify(object); - } catch (err) { - try { - return JSON.stringify(object, safeCycles()); - } catch (err2) { - console.log('err', err); - console.log('err2', err2); - console.log('object', object); - return 'modules/scripts/log: something is wrong'; - } - } -} - -function logMethod(process, console, type) { - return (object) => { - const { name, msg, force = false } = object; - let formatedMsg = msg; - - if (process.env.NODE_ENV === 'test' && !force) { - return; - } - - if (process.env.NODE_ENV !== 'production' && !name) { - throw new Error(`Missing name ${JSON.stringify(object)}`); - } - - const format = - process.env.NODE_ENV === 'production' && - process.env.LOG_FORMAT !== 'human' && - !process.browser - ? 'json' - : 'human'; - - if (formatedMsg.duration) { - formatedMsg = serializeDuration(formatedMsg); - } - - if (format === 'json') { - if (formatedMsg.err) { - formatedMsg = serializeErr(formatedMsg); - } - - const message = fastAndSafeJsonStringify({ - level: logTypes[type].level, - msg: formatedMsg, - name, - ...(process.browser ? {} : { pid: process.pid }), - }); - - if (process.browser) { - console[logTypes[type].method](message); - return; - } - - // Faster than calling console.x. - process.stdout.write(`${message}\n`); - } else { - const messages = []; - - if (process.browser) { - messages.push(`${name}:`); - } else { - messages.push(`${type.toUpperCase()} ${process.pid} ${name}:`); - } - - if (formatedMsg.err) { - messages.push(formatedMsg.err); - delete formatedMsg.err; - } - messages.push(formatedMsg); - - console[logTypes[type].method](...messages); - } - }; -} - -const log = { - info: logMethod(process, console, 'info'), - warn: logMethod(process, console, 'warn'), - error: logMethod(process, console, 'error'), - fatal: logMethod(process, console, 'fatal'), -}; - -module.exports = log; diff --git a/modules/waterfall/Batcher.js b/modules/waterfall/Batcher.js deleted file mode 100644 index 1b0e8c21716f46..00000000000000 --- a/modules/waterfall/Batcher.js +++ /dev/null @@ -1,47 +0,0 @@ -// Inspired by https://caolan.github.io/async/v3/docs.html#cargo -// The main difference is that we have a timeout. -class Batcher { - pendingEntries = []; - - timeout = null; - - context = {}; - - constructor(worker, options = {}) { - // max waiting time before flushing the pending entries (process them) - this.maxWait = options.maxWait || 1000; - // max number of entries in the queue before flushing them (process them) - this.maxItems = options.maxItems || 100; - this.worker = worker; - } - - // public method - push(entries, contextItem) { - this.context = contextItem; - this.pendingEntries = this.pendingEntries.concat(entries); - - if (this.pendingEntries.length >= this.maxItems) { - return this.sendItems(); - } - - clearTimeout(this.timeout); - this.timeout = setTimeout(() => { - this.sendItems(); - }, this.maxWait); - - return null; - } - - sendItems() { - const pendingEntries = this.pendingEntries.splice(0); // Transfer the item to the job. - clearTimeout(this.timeout); - return this.worker(pendingEntries, this.context); - } - - clear() { - clearTimeout(this.timeout); - this.pendingEntries = []; - } -} - -export default Batcher; diff --git a/modules/waterfall/forEach.js b/modules/waterfall/forEach.js deleted file mode 100644 index ad11eed70dcd27..00000000000000 --- a/modules/waterfall/forEach.js +++ /dev/null @@ -1,8 +0,0 @@ -async function forEach(array, iteratee) { - for (let i = 0; i < array.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - await iteratee(array[i], i); - } -} - -export default forEach; diff --git a/modules/waterfall/metric.js b/modules/waterfall/metric.js deleted file mode 100644 index 98a0abbc87ff10..00000000000000 --- a/modules/waterfall/metric.js +++ /dev/null @@ -1,129 +0,0 @@ -// The API is inspired by console.time -// The implementation is isomorphic. - -const times = new Map(); - -const implementations = { - mark: { - start: (name) => { - times.set(name, performance.now()); - performance.mark(`metric_${name}_start`); - }, - end: (name) => { - const endMark = `metric_${name}_end`; - performance.mark(endMark); - const startMark = `metric_${name}_start`; - performance.measure(name, startMark, endMark); - const duration = performance.getEntriesByName(name)[0].duration; - return duration; - }, - }, - now: { - start: (name) => { - times.set(name, performance.now()); - }, - end: (name) => { - const time = times.get(name); - const duration = performance.now() - time; - return duration; - }, - }, - hrtime: { - start: (name) => { - // https://nodejs.org/api/process.html#process_process_hrtime_time - times.set(name, process.hrtime()); - }, - end: (name) => { - const time = times.get(name); - const durations = process.hrtime(time); - const duration = durations[0] / 1e3 + durations[1] / 1e6; - return duration; - }, - }, -}; - -let getImplementationCache; - -function getImplementation() { - if (getImplementationCache) { - return getImplementationCache; - } - - if (typeof performance !== 'undefined' && performance.mark) { - getImplementationCache = implementations.mark; - } else if (typeof performance !== 'undefined' && performance.now) { - getImplementationCache = implementations.now; - } else if (process.hrtime) { - getImplementationCache = implementations.hrtime; - } else { - throw new Error('No performance API available'); - } - - return getImplementationCache; -} - -class Metric { - /** - * Call to begin a measurement. - */ - static start(name) { - if (process.env.NODE_ENV !== 'production') { - if (times.get(name)) { - console.error('Recording already started'); - } - } - getImplementation().start(name); - } - - /** - * Returns the duration of the timing metric. The unit is milliseconds. - * @type {number} - */ - static end(name) { - if (!times.get(name)) { - throw new Error(`No such name '${name}' for metric`); - } - - const duration = getImplementation().end(name); - times.delete(name); - return duration; - } - - name = ''; - - /** - * @param {string} name A name for the metric. - */ - constructor(name) { - if (!name) { - throw new Error('Please provide a metric name'); - } - - this.name = name; - } - - /** - * Call to begin a measurement. - */ - start(name) { - if (name) { - throw new Error('The name argument is not supported'); - } - - Metric.start(this.name); - } - - /** - * Returns the duration of the timing metric. The unit is milliseconds. - * @type {number} - */ - end(name) { - if (name) { - throw new Error('The name argument is not supported'); - } - - return Metric.end(this.name); - } -} - -export default Metric; diff --git a/packages/material-ui/test/umd/run.js b/packages/material-ui/test/umd/run.js index 36746666389481..f7e6d1ebcc651e 100644 --- a/packages/material-ui/test/umd/run.js +++ b/packages/material-ui/test/umd/run.js @@ -4,46 +4,38 @@ const http = require('http'); const path = require('path'); const express = require('express'); const expect = require('expect-puppeteer'); -const { addTeardown, shutdown } = require('../../../../modules/handleKillSignals'); -const log = require('../../../../modules/log'); const port = 3090; const host = '0.0.0.0'; function startServer(app) { - return new Promise((resolve, reject) => { - const server = http.createServer(app); - server.listen(port, host, (err) => { - if (err) { - reject(err); - return; - } - - log.info({ - name: 'http', - msg: `ready on http://${server.address().address}:${server.address().port}`, + const server = http.createServer(app); + + function close() { + // eslint-disable-next-line no-console + console.info('http: server is stopping'); + + return new Promise((resolve, reject) => { + server.close((error) => { + if (error) { + reject(error); + } else { + resolve(); + } }); - - resolve(); }); + } - addTeardown({ - callback: () => { - log.info({ - name: 'http', - msg: 'server is stopping', - }); - return new Promise((resolve2, reject2) => { - server.close((err) => { - if (err) { - reject2(err); - return; - } - resolve2(); - }); - }); - }, - nice: 1, // Do it first. + return new Promise((resolve, reject) => { + server.listen(port, host, (error) => { + if (error) { + reject(error); + } else { + // eslint-disable-next-line no-console + console.info(`http: ready on http://${server.address().address}:${server.address().port}`); + + resolve({ close }); + } }); }); } @@ -95,10 +87,8 @@ function App() { } async function startBrowser() { - log.info({ - name: 'browser', - msg: 'start', - }); + // eslint-disable-next-line no-console + console.info('browser: start'); const browser = await puppeteer.launch({ args: [ '--single-process', // Solve mono-thread issue on CircleCI @@ -112,46 +102,34 @@ async function startBrowser() { throw err; }); - addTeardown({ - callback: () => { - log.info({ - name: 'browser', - msg: 'server is stopping', - }); - return browser.close(); - }, - nice: 2, - }); + function close() { + // eslint-disable-next-line no-console + console.info('browser:server is stopping'); + return browser.close(); + } - return page; + return { page, close }; } -process.on('unhandledRejection', (reason, promise) => { - log.fatal({ - name: 'unhandledRejection', - msg: { reason, promise }, - }); - process.exit(1); -}); - async function run() { + let server = { close() {} }; + let closeBrowser = () => {}; try { const app = await createApp(); - await startServer(app); + server = await startServer(app); + + const { page, close } = await startBrowser(); + closeBrowser = close; - const page = await startBrowser(); await page.goto(`http://${host}:${port}`); await expect(page).toClick('button', { text: 'Super Secret Password' }); await expect(page).toMatch('1-2-3-4-5'); - } catch (err) { - log.fatal({ - name: 'test', - msg: { err }, - }); - process.exit(1); + } finally { + await Promise.all([closeBrowser(), server.close()]); } - - shutdown('SIGINT', 'done'); } -run(); +run().catch((error) => { + console.error('test: ', error); + process.exit(1); +});