From dcd342fa74309af2cccb9e4f05d934fea8da909b Mon Sep 17 00:00:00 2001
From: Ben Noordhuis <info@bnoordhuis.nl>
Date: Sat, 20 Jan 2018 01:00:34 +0100
Subject: [PATCH 1/6] tools: teach gyp to write an 'all deps' rule

Make GYP write a .deps file in the top-level directory that we can use
in the Makefile to get a proper dependency chain for the `node` target.
Preparatory work for getting rid of recursive make invocations.
---
 tools/gyp/pylib/gyp/generator/make.py | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/tools/gyp/pylib/gyp/generator/make.py b/tools/gyp/pylib/gyp/generator/make.py
index f7f519b3e6b848..eaf02ae734bded 100644
--- a/tools/gyp/pylib/gyp/generator/make.py
+++ b/tools/gyp/pylib/gyp/generator/make.py
@@ -2136,6 +2136,7 @@ def CalculateMakefilePath(build_file, base_name):
     for target in gyp.common.AllTargets(target_list, target_dicts, build_file):
       needed_targets.add(target)
 
+  all_deps = set()
   build_files = set()
   include_list = set()
   for qualified_target in target_list:
@@ -2184,6 +2185,12 @@ def CalculateMakefilePath(build_file, base_name):
                                               os.path.dirname(makefile_path))
     include_list.add(mkfile_rel_path)
 
+    if 'actions' in spec:
+      for action in spec['actions']:
+        all_deps.update(map(writer.Absolutify, action['inputs']))
+    if 'sources' in spec:
+      all_deps.update(map(writer.Absolutify, spec['sources']))
+
   # Write out per-gyp (sub-project) Makefiles.
   depth_rel_path = gyp.common.RelativePath(options.depth, os.getcwd())
   for build_file in build_files:
@@ -2227,3 +2234,10 @@ def CalculateMakefilePath(build_file, base_name):
   root_makefile.write(SHARED_FOOTER)
 
   root_makefile.close()
+
+  # Hack to get rid of $(obj)/path/to/foo.o deps that node.gyp adds manually.
+  all_deps = [s for s in all_deps if not '$' in s]
+  all_deps_path = os.path.join(options.toplevel_dir, '.deps')
+  with open(all_deps_path, 'w') as f:
+    f.write('ALL_DEPS := \\\n\t')
+    f.write(' \\\n\t'.join(sorted(all_deps)))

From 4126c5444a22267caa64c603debe6527fd9530a1 Mon Sep 17 00:00:00 2001
From: Ben Noordhuis <info@bnoordhuis.nl>
Date: Sat, 20 Jan 2018 01:00:34 +0100
Subject: [PATCH 2/6] tools: simplify tools/doc/addon-verify.js

Make the script synchronous and clean it up.  This is preparatory work
for follow-ups commits.
---
 tools/doc/addon-verify.js | 103 ++++++++++++++++----------------------
 1 file changed, 44 insertions(+), 59 deletions(-)

diff --git a/tools/doc/addon-verify.js b/tools/doc/addon-verify.js
index 2e72abb77f925f..4da99d64d72da1 100644
--- a/tools/doc/addon-verify.js
+++ b/tools/doc/addon-verify.js
@@ -1,5 +1,6 @@
 'use strict';
 
+const { strictEqual } = require('assert');
 const fs = require('fs');
 const path = require('path');
 const marked = require('marked');
@@ -8,52 +9,36 @@ const rootDir = path.resolve(__dirname, '..', '..');
 const doc = path.resolve(rootDir, 'doc', 'api', 'addons.md');
 const verifyDir = path.resolve(rootDir, 'test', 'addons');
 
-const contents = fs.readFileSync(doc).toString();
-
-const tokens = marked.lexer(contents);
 let id = 0;
-
 let currentHeader;
+
 const addons = {};
-tokens.forEach((token) => {
-  if (token.type === 'heading' && token.text) {
-    currentHeader = token.text;
+const content = fs.readFileSync(doc, 'utf8');
+for (const { text, type } of marked.lexer(content)) {
+  if (type === 'heading' && text) {
+    currentHeader = text;
     addons[currentHeader] = {
       files: {}
     };
   }
-  if (token.type === 'code') {
-    var match = token.text.match(/^\/\/\s+(.*\.(?:cc|h|js))[\r\n]/);
+  if (type === 'code') {
+    const match = text.match(/^\/\/\s+(.*\.(?:cc|h|js))[\r\n]/);
     if (match !== null) {
-      addons[currentHeader].files[match[1]] = token.text;
+      addons[currentHeader].files[match[1]] = text;
     }
   }
-});
-for (var header in addons) {
-  verifyFiles(addons[header].files,
-              header,
-              console.log.bind(null, 'wrote'),
-              function(err) { if (err) throw err; });
 }
 
-function once(fn) {
-  var once = false;
-  return function() {
-    if (once)
-      return;
-    once = true;
-    fn.apply(this, arguments);
-  };
-}
+for (const header in addons) {
+  let { files } = addons[header];
 
-function verifyFiles(files, blockName, onprogress, ondone) {
   // must have a .cc and a .js to be a valid test
   if (!Object.keys(files).some((name) => /\.cc$/.test(name)) ||
       !Object.keys(files).some((name) => /\.js$/.test(name))) {
-    return;
+    continue;
   }
 
-  blockName = blockName
+  const blockName = header
     .toLowerCase()
     .replace(/\s/g, '_')
     .replace(/[^a-z\d_]/g, '');
@@ -62,21 +47,9 @@ function verifyFiles(files, blockName, onprogress, ondone) {
     `${(++id < 10 ? '0' : '') + id}_${blockName}`
   );
 
-  files = Object.keys(files).map(function(name) {
-    if (name === 'test.js') {
-      files[name] = `'use strict';
-const common = require('../../common');
-${files[name].replace(
-    "'./build/Release/addon'",
-    // eslint-disable-next-line no-template-curly-in-string
-    '`./build/${common.buildType}/addon`')}
-`;
-    }
-    return {
-      path: path.resolve(dir, name),
-      name: name,
-      content: files[name]
-    };
+  files = Object.entries(files).map(([name, content]) => {
+    if (name === 'test.js') content = boilerplate(name, content);
+    return { name, content, path: path.resolve(dir, name) };
   });
 
   files.push({
@@ -84,7 +57,7 @@ ${files[name].replace(
     content: JSON.stringify({
       targets: [
         {
-          target_name: 'addon',
+          target_name: 'binding',
           defines: [ 'V8_DEPRECATION_WARNINGS=1' ],
           sources: files.map(function(file) {
             return file.name;
@@ -94,22 +67,34 @@ ${files[name].replace(
     })
   });
 
-  fs.mkdir(dir, function() {
-    // Ignore errors
+  try {
+    fs.mkdirSync(dir);
+  } catch (e) {
+    strictEqual(e.code, 'EEXIST');
+  }
 
-    const done = once(ondone);
-    var waiting = files.length;
-    files.forEach(function(file) {
-      fs.writeFile(file.path, file.content, function(err) {
-        if (err)
-          return done(err);
+  for (const file of files) {
+    let content;
+    try {
+      content = fs.readFileSync(file.path, 'utf8');
+    } catch (e) {
+      strictEqual(e.code, 'ENOENT');
+    }
 
-        if (onprogress)
-          onprogress(file.path);
+    // Only update when file content has changed to prevent unneeded rebuilds.
+    if (content !== file.content) {
+      fs.writeFileSync(file.path, file.content);
+      console.log('wrote', file.path);
+    }
+  }
+}
 
-        if (--waiting === 0)
-          done();
-      });
-    });
-  });
+function boilerplate(name, content) {
+  return `'use strict';
+const common = require('../../common');
+${content.replace(
+    "'./build/Release/binding'",
+    // eslint-disable-next-line no-template-curly-in-string
+    '`./build/${common.buildType}/binding`')}
+`;
 }

From fb0972dca42ba6265ea1634544b1e4bff92d5180 Mon Sep 17 00:00:00 2001
From: Ben Noordhuis <info@bnoordhuis.nl>
Date: Sat, 20 Jan 2018 01:00:34 +0100
Subject: [PATCH 3/6] build,test: make building addon tests less fragile

* Get rid of recursive `make` when building the node binary.  An earlier
  commit makes GYP write out rules that we can use for proper dependency
  tracking.

* Use module name 'binding' in addons.md and addons-napi/*/binding.gyp.
  This massively simplifies the logic for generating the build rules.

* Check in auto-generated add-on tests from `doc/api/addons.md`.  The
  files change rarely and generating them dynamically causes no end of
  race conditions and special-casing during the build.
---
 .gitignore                                    |   1 -
 Makefile                                      | 262 ++++++------------
 doc/api/addons.md                             | 101 +++----
 node.gyp                                      |  12 +-
 test/addons-napi/test_array/binding.gyp       |   2 +-
 test/addons-napi/test_array/test.js           |   2 +-
 test/addons-napi/test_async/binding.gyp       |   2 +-
 .../test_async/test-async-hooks.js            |   2 +-
 test/addons-napi/test_async/test.js           |   2 +-
 test/addons-napi/test_buffer/binding.gyp      |   2 +-
 test/addons-napi/test_buffer/test.js          |   2 +-
 test/addons-napi/test_constructor/binding.gyp |   6 +-
 test/addons-napi/test_constructor/test.js     |   2 +-
 .../test_constructor_name/binding.gyp         |   8 +
 .../test.js}                                  |   3 +-
 .../test_constructor_name.c                   |   0
 test/addons-napi/test_conversions/binding.gyp |   2 +-
 test/addons-napi/test_conversions/test.js     |   2 +-
 test/addons-napi/test_dataview/binding.gyp    |   2 +-
 test/addons-napi/test_dataview/test.js        |   2 +-
 test/addons-napi/test_env_sharing/binding.gyp |   2 +-
 test/addons-napi/test_env_sharing/test.js     |   2 +-
 test/addons-napi/test_error/binding.gyp       |   2 +-
 test/addons-napi/test_error/test.js           |   2 +-
 test/addons-napi/test_exception/binding.gyp   |   2 +-
 test/addons-napi/test_exception/test.js       |   2 +-
 test/addons-napi/test_fatal/binding.gyp       |   2 +-
 test/addons-napi/test_fatal/test.js           |   2 +-
 test/addons-napi/test_fatal/test2.js          |   2 +-
 test/addons-napi/test_function/binding.gyp    |   2 +-
 test/addons-napi/test_function/test.js        |   2 +-
 test/addons-napi/test_general/binding.gyp     |   2 +-
 test/addons-napi/test_general/test.js         |   2 +-
 test/addons-napi/test_general/testGlobals.js  |   2 +-
 .../test_general/testInstanceOf.js            |   2 +-
 test/addons-napi/test_general/testNapiRun.js  |   2 +-
 .../test_general/testNapiStatus.js            |   2 +-
 .../addons-napi/test_handle_scope/binding.gyp |   2 +-
 test/addons-napi/test_handle_scope/test.js    |   3 +-
 test/addons-napi/test_number/binding.gyp      |   2 +-
 test/addons-napi/test_number/test.js          |   2 +-
 test/addons-napi/test_object/binding.gyp      |   2 +-
 test/addons-napi/test_object/test.js          |   2 +-
 test/addons-napi/test_promise/binding.gyp     |   2 +-
 test/addons-napi/test_promise/test.js         |   2 +-
 test/addons-napi/test_properties/binding.gyp  |   2 +-
 test/addons-napi/test_properties/test.js      |   2 +-
 test/addons-napi/test_reference/binding.gyp   |   2 +-
 test/addons-napi/test_reference/test.js       |   2 +-
 test/addons-napi/test_string/binding.gyp      |   2 +-
 test/addons-napi/test_string/test.js          |   2 +-
 test/addons-napi/test_symbol/binding.gyp      |   2 +-
 test/addons-napi/test_symbol/test1.js         |   2 +-
 test/addons-napi/test_symbol/test2.js         |   2 +-
 test/addons-napi/test_symbol/test3.js         |   2 +-
 test/addons-napi/test_typedarray/binding.gyp  |   2 +-
 test/addons-napi/test_typedarray/test.js      |   2 +-
 test/addons-napi/test_uv_loop/binding.gyp     |   2 +-
 test/addons-napi/test_uv_loop/test.js         |   2 +-
 test/addons-napi/test_warning/binding.gyp     |   2 +-
 test/addons-napi/test_warning/test.js         |   2 +-
 test/addons/.gitignore                        |   2 -
 test/addons/01_function_arguments/binding.cc  |  52 ++++
 test/addons/01_function_arguments/binding.gyp |   2 +
 test/addons/01_function_arguments/test.js     |   7 +
 test/addons/02_callbacks/binding.cc           |  30 ++
 test/addons/02_callbacks/binding.gyp          |   2 +
 test/addons/02_callbacks/test.js              |  10 +
 test/addons/03_object_factory/binding.cc      |  29 ++
 test/addons/03_object_factory/binding.gyp     |   2 +
 test/addons/03_object_factory/test.js         |  10 +
 test/addons/04_function_factory/binding.cc    |  39 +++
 test/addons/04_function_factory/binding.gyp   |   2 +
 test/addons/04_function_factory/test.js       |   9 +
 test/addons/05_wrapping_c_objects/binding.cc  |  17 ++
 test/addons/05_wrapping_c_objects/binding.gyp |   2 +
 test/addons/05_wrapping_c_objects/myobject.cc |  73 +++++
 test/addons/05_wrapping_c_objects/myobject.h  |  27 ++
 test/addons/05_wrapping_c_objects/test.js     |  13 +
 .../06_factory_of_wrapped_objects/binding.cc  |  27 ++
 .../06_factory_of_wrapped_objects/binding.gyp |   2 +
 .../06_factory_of_wrapped_objects/myobject.cc |  83 ++++++
 .../06_factory_of_wrapped_objects/myobject.h  |  28 ++
 .../06_factory_of_wrapped_objects/test.js     |  21 ++
 .../binding.cc                                |  42 +++
 .../binding.gyp                               |   2 +
 .../myobject.cc                               |  70 +++++
 .../myobject.h                                |  28 ++
 .../07_passing_wrapped_objects_around/test.js |  12 +
 .../08_void_atexitcallback_args/binding.cc    |  47 ++++
 .../08_void_atexitcallback_args/binding.gyp   |   2 +
 .../08_void_atexitcallback_args/test.js       |   5 +
 .../openssl-client-cert-engine/binding.cc     |   6 +
 .../openssl-client-cert-engine/binding.gyp    |   4 +
 tools/doc/addon-verify.js                     |  11 +-
 95 files changed, 917 insertions(+), 301 deletions(-)
 create mode 100644 test/addons-napi/test_constructor_name/binding.gyp
 rename test/addons-napi/{test_constructor/test2.js => test_constructor_name/test.js} (69%)
 rename test/addons-napi/{test_constructor => test_constructor_name}/test_constructor_name.c (100%)
 create mode 100644 test/addons/01_function_arguments/binding.cc
 create mode 100644 test/addons/01_function_arguments/binding.gyp
 create mode 100644 test/addons/01_function_arguments/test.js
 create mode 100644 test/addons/02_callbacks/binding.cc
 create mode 100644 test/addons/02_callbacks/binding.gyp
 create mode 100644 test/addons/02_callbacks/test.js
 create mode 100644 test/addons/03_object_factory/binding.cc
 create mode 100644 test/addons/03_object_factory/binding.gyp
 create mode 100644 test/addons/03_object_factory/test.js
 create mode 100644 test/addons/04_function_factory/binding.cc
 create mode 100644 test/addons/04_function_factory/binding.gyp
 create mode 100644 test/addons/04_function_factory/test.js
 create mode 100644 test/addons/05_wrapping_c_objects/binding.cc
 create mode 100644 test/addons/05_wrapping_c_objects/binding.gyp
 create mode 100644 test/addons/05_wrapping_c_objects/myobject.cc
 create mode 100644 test/addons/05_wrapping_c_objects/myobject.h
 create mode 100644 test/addons/05_wrapping_c_objects/test.js
 create mode 100644 test/addons/06_factory_of_wrapped_objects/binding.cc
 create mode 100644 test/addons/06_factory_of_wrapped_objects/binding.gyp
 create mode 100644 test/addons/06_factory_of_wrapped_objects/myobject.cc
 create mode 100644 test/addons/06_factory_of_wrapped_objects/myobject.h
 create mode 100644 test/addons/06_factory_of_wrapped_objects/test.js
 create mode 100644 test/addons/07_passing_wrapped_objects_around/binding.cc
 create mode 100644 test/addons/07_passing_wrapped_objects_around/binding.gyp
 create mode 100644 test/addons/07_passing_wrapped_objects_around/myobject.cc
 create mode 100644 test/addons/07_passing_wrapped_objects_around/myobject.h
 create mode 100644 test/addons/07_passing_wrapped_objects_around/test.js
 create mode 100644 test/addons/08_void_atexitcallback_args/binding.cc
 create mode 100644 test/addons/08_void_atexitcallback_args/binding.gyp
 create mode 100644 test/addons/08_void_atexitcallback_args/test.js
 create mode 100644 test/addons/openssl-client-cert-engine/binding.cc

diff --git a/.gitignore b/.gitignore
index 0ff301ace3824d..a6e530f2496331 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,7 +74,6 @@ ipch/
 /npm.wxs
 /tools/msvs/npm.wixobj
 /tools/msvs/genfiles/
-/test/addons/??_*/
 email.md
 deps/v8-*
 deps/icu
diff --git a/Makefile b/Makefile
index db001a5350b1e4..f988bb4e3681f6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,4 @@
+-include .deps  # Generated by GYP.
 -include config.mk
 
 BUILDTYPE ?= Release
@@ -65,9 +66,9 @@ V ?= 1
 # BUILDTYPE=Debug builds both release and debug builds. If you want to compile
 # just the debug build, run `make -C out BUILDTYPE=Debug` instead.
 ifeq ($(BUILDTYPE),Release)
-all: out/Makefile $(NODE_EXE) ## Default target, builds node in out/Release/node.
+all: $(NODE_EXE) ## Default target, builds node in out/Release/node.
 else
-all: out/Makefile $(NODE_EXE) $(NODE_G_EXE)
+all: $(NODE_EXE) $(NODE_G_EXE)
 endif
 
 .PHONY: help
@@ -77,32 +78,24 @@ help: ## Print help for targets with comments.
 	@grep -E '^[a-zA-Z0-9._-]+:.*?## .*$$' Makefile | sort | \
 		awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
 
-# The .PHONY is needed to ensure that we recursively use the out/Makefile
-# to check for changes.
-.PHONY: $(NODE_EXE) $(NODE_G_EXE)
+$(NODE_EXE): out/Release/node
+	ln -fs $< $@
 
-# The -r/-L check stops it recreating the link if it is already in place,
-# otherwise $(NODE_EXE) being a .PHONY target means it is always re-run.
-# Without the check there is a race condition between the link being deleted
-# and recreated which can break the addons build when running test-ci
-# See comments on the build-addons target for some more info
-$(NODE_EXE): config.gypi out/Makefile
+$(NODE_G_EXE): out/Debug/node
+	ln -fs $< $@
+
+out/Release/node: out/Makefile $(ALL_DEPS)
 	$(MAKE) -C out BUILDTYPE=Release V=$(V)
-	if [ ! -r $@ -o ! -L $@ ]; then ln -fs out/Release/$(NODE_EXE) $@; fi
 
-$(NODE_G_EXE): config.gypi out/Makefile
+out/Debug/node: out/Makefile $(ALL_DEPS)
 	$(MAKE) -C out BUILDTYPE=Debug V=$(V)
-	if [ ! -r $@ -o ! -L $@ ]; then ln -fs out/Debug/$(NODE_EXE) $@; fi
 
-out/Makefile: common.gypi deps/uv/uv.gyp deps/http_parser/http_parser.gyp \
+out/Makefile .deps: deps/uv/uv.gyp deps/http_parser/http_parser.gyp \
               deps/zlib/zlib.gyp deps/v8/gypfiles/toolchain.gypi \
               deps/v8/gypfiles/features.gypi deps/v8/src/v8.gyp node.gyp \
-              config.gypi
+              common.gypi config.gypi
 	$(PYTHON) tools/gyp_node.py -f make
 
-config.gypi: configure
-	$(error Missing or stale $@, please run ./$<)
-
 .PHONY: install
 install: all ## Installs node into $PREFIX (default=/usr/local).
 	$(PYTHON) tools/install.py $@ '$(DESTDIR)' '$(PREFIX)'
@@ -232,9 +225,7 @@ v8:
 
 .PHONY: test
 # This does not run tests of third-party libraries inside deps.
-test: all ## Runs default tests, linters, and builds docs.
-	$(MAKE) -s build-addons
-	$(MAKE) -s build-addons-napi
+test: all build-addons	## Runs default tests, linters, and builds docs.
 	$(MAKE) -s doc-only
 	$(MAKE) -s lint
 	$(MAKE) -s cctest
@@ -244,18 +235,14 @@ test: all ## Runs default tests, linters, and builds docs.
 		$(CI_DOC)
 
 .PHONY: test-only
-test-only: all  ## For a quick test, does not run linter or build docs.
-	$(MAKE) build-addons
-	$(MAKE) build-addons-napi
+test-only: all build-addons	## For a quick test, does not run linter or build docs.
 	$(MAKE) cctest
 	$(PYTHON) tools/test.py --mode=release -J \
 		$(CI_JS_SUITES) \
 		$(CI_NATIVE_SUITES)
 
 # Used by `make coverage-test`
-test-cov: all
-	$(MAKE) build-addons
-	$(MAKE) build-addons-napi
+test-cov: all build-addons
 	# $(MAKE) cctest
 	$(PYTHON) tools/test.py --mode=release -J \
 		$(CI_JS_SUITES) \
@@ -271,115 +258,53 @@ test-valgrind: all
 test-check-deopts: all
 	$(PYTHON) tools/test.py --mode=release --check-deopts parallel sequential -J
 
-benchmark/misc/function_call/build/Release/binding.node: all \
-		benchmark/misc/function_call/binding.cc \
-		benchmark/misc/function_call/binding.gyp
-	$(NODE) deps/npm/node_modules/node-gyp/bin/node-gyp rebuild \
-		--python="$(PYTHON)" \
-		--directory="$(shell pwd)/benchmark/misc/function_call" \
-		--nodedir="$(shell pwd)"
-
-# Implicitly depends on $(NODE_EXE).  We don't depend on it explicitly because
-# it always triggers a rebuild due to it being a .PHONY rule.  See the comment
-# near the build-addons rule for more background.
-test/gc/build/Release/binding.node: test/gc/binding.cc test/gc/binding.gyp
-	$(NODE) deps/npm/node_modules/node-gyp/bin/node-gyp rebuild \
-		--python="$(PYTHON)" \
-		--directory="$(shell pwd)/test/gc" \
-		--nodedir="$(shell pwd)"
-
-DOCBUILDSTAMP_PREREQS = tools/doc/addon-verify.js doc/api/addons.md
+ADDON_PREREQS := \
+	common.gypi \
+	config.gypi \
+	deps/npm/node_modules/node-gyp/package.json \
+	src/node.h \
+	src/node_api.h \
+	src/node_api_types.h \
+	src/node_buffer.h \
+	src/node_object_wrap.h \
+	src/node_version.h \
+	$(wildcard deps/openssl/openssl/include/openssl/*.h) \
+	$(wildcard deps/uv/include/*.h) \
+	$(wildcard deps/v8/include/*.h) \
+	$(wildcard deps/zlib/*.h) \
 
 ifeq ($(OSTYPE),aix)
-DOCBUILDSTAMP_PREREQS := $(DOCBUILDSTAMP_PREREQS) out/$(BUILDTYPE)/node.exp
+ADDON_PREREQS := $(ADDON_PREREQS) out/$(BUILDTYPE)/node.exp
 endif
 
-test/addons/.docbuildstamp: $(DOCBUILDSTAMP_PREREQS)
-	$(RM) -r test/addons/??_*/
-	[ -x $(NODE) ] && $(NODE) $< || node $<
-	touch $@
-
-ADDONS_BINDING_GYPS := \
-	$(filter-out test/addons/??_*/binding.gyp, \
+ADDON_DIRS := \
+	$(dir benchmark/misc/function_call/ test/gc/ \
+		$(wildcard test/addons-napi/*/binding.gyp) \
 		$(wildcard test/addons/*/binding.gyp))
 
-ADDONS_BINDING_SOURCES := \
-	$(filter-out test/addons/??_*/*.cc, $(wildcard test/addons/*/*.cc)) \
-	$(filter-out test/addons/??_*/*.h, $(wildcard test/addons/*/*.h))
+ADDON_FILES := \
+	$(foreach d, $(ADDON_DIRS), $(d)build/$(BUILDTYPE)/binding.node)
 
-# Implicitly depends on $(NODE_EXE), see the build-addons rule for rationale.
-# Depends on node-gyp package.json so that build-addons is (re)executed when
-# node-gyp is updated as part of an npm update.
-test/addons/.buildstamp: config.gypi \
-	deps/npm/node_modules/node-gyp/package.json \
-	$(ADDONS_BINDING_GYPS) $(ADDONS_BINDING_SOURCES) \
-	deps/uv/include/*.h deps/v8/include/*.h \
-	src/node.h src/node_buffer.h src/node_object_wrap.h src/node_version.h \
-	test/addons/.docbuildstamp
-#	Cannot use $(wildcard test/addons/*/) here, it's evaluated before
-#	embedded addons have been generated from the documentation.
-#	Ignore folders without binding.gyp
-#	(https://github.com/nodejs/node/issues/14843)
-	@for dirname in test/addons/*/; do \
-		if [ ! -f "$$PWD/$${dirname}binding.gyp" ]; then \
-			continue; fi ; \
-		printf "\nBuilding addon $$PWD/$$dirname\n" ; \
-		env MAKEFLAGS="-j1" $(NODE) deps/npm/node_modules/node-gyp/bin/node-gyp \
-		        --loglevel=$(LOGLEVEL) rebuild \
-			--python="$(PYTHON)" \
-			--directory="$$PWD/$$dirname" \
-			--nodedir="$$PWD" || exit 1 ; \
-	done
-	touch $@
+NODE_GYP := \
+	env MAKEFLAGS="-j1" \
+	$(NODE) deps/npm/node_modules/node-gyp/bin/node-gyp \
+	--loglevel="$(LOGLEVEL)" --nodedir="$(CURDIR)" --python="$(PYTHON)"
 
-.PHONY: build-addons
-# .buildstamp needs $(NODE_EXE) but cannot depend on it
-# directly because it calls make recursively.  The parent make cannot know
-# if the subprocess touched anything so it pessimistically assumes that
-# .buildstamp is out of date and need a rebuild.
-# Just goes to show that recursive make really is harmful...
-# TODO(bnoordhuis) Force rebuild after gyp update.
-build-addons: | $(NODE_EXE) test/addons/.buildstamp
-
-ADDONS_NAPI_BINDING_GYPS := \
-	$(filter-out test/addons-napi/??_*/binding.gyp, \
-		$(wildcard test/addons-napi/*/binding.gyp))
-
-ADDONS_NAPI_BINDING_SOURCES := \
-	$(filter-out test/addons-napi/??_*/*.cc, $(wildcard test/addons-napi/*/*.cc)) \
-	$(filter-out test/addons-napi/??_*/*.h, $(wildcard test/addons-napi/*/*.h))
-
-# Implicitly depends on $(NODE_EXE), see the build-addons-napi rule for rationale.
-test/addons-napi/.buildstamp: config.gypi \
-	deps/npm/node_modules/node-gyp/package.json \
-	$(ADDONS_NAPI_BINDING_GYPS) $(ADDONS_NAPI_BINDING_SOURCES) \
-	deps/uv/include/*.h deps/v8/include/*.h \
-	src/node.h src/node_buffer.h src/node_object_wrap.h src/node_version.h \
-	src/node_api.h src/node_api_types.h
-#	Cannot use $(wildcard test/addons-napi/*/) here, it's evaluated before
-#	embedded addons have been generated from the documentation.
-#	Ignore folders without binding.gyp
-#	(https://github.com/nodejs/node/issues/14843)
-	@for dirname in test/addons-napi/*/; do \
-		if [ ! -f "$$PWD/$${dirname}binding.gyp" ]; then \
-			continue; fi ; \
-		printf "\nBuilding addon $$PWD/$$dirname\n" ; \
-		env MAKEFLAGS="-j1" $(NODE) deps/npm/node_modules/node-gyp/bin/node-gyp \
-		        --loglevel=$(LOGLEVEL) rebuild \
-			--python="$(PYTHON)" \
-			--directory="$$PWD/$$dirname" \
-			--nodedir="$$PWD" || exit 1 ; \
-	done
-	touch $@
+define do_addon
+$(1)build/Makefile: $(1)binding.gyp common.gypi
+	$(NODE_GYP) --directory=$(1) configure
+$(1)build/Release/.buildstamp: $(1)build/Makefile $(2) $(ADDON_PREREQS)
+	$(NODE_GYP) --directory=$(1) build
+	@touch $$@
+$(1)build/Release/binding.node: $(1)build/Release/.buildstamp
+endef
 
-.PHONY: build-addons-napi
-# .buildstamp needs $(NODE_EXE) but cannot depend on it
-# directly because it calls make recursively.  The parent make cannot know
-# if the subprocess touched anything so it pessimistically assumes that
-# .buildstamp is out of date and need a rebuild.
-# Just goes to show that recursive make really is harmful...
-# TODO(bnoordhuis) Force rebuild after gyp or node-gyp update.
-build-addons-napi: | $(NODE_EXE) test/addons-napi/.buildstamp
+$(foreach x, $(ADDON_DIRS), \
+	$(eval $(call do_addon,$(x),$(wildcard $(x)/*.{c,cc,h}))))
+
+.PHONY: build-addons
+build-addons: $(NODE)
+	@$(MAKE) -s $(ADDON_FILES)
 
 .PHONY: clear-stalled
 clear-stalled:
@@ -398,15 +323,11 @@ test-gc: all test/gc/build/Release/binding.node
 test-gc-clean:
 	$(RM) -r test/gc/build
 
-test-build: | all build-addons build-addons-napi
-
-test-build-addons-napi: all build-addons-napi
-
 .PHONY: test-all
-test-all: test-build test/gc/build/Release/binding.node ## Run everything in test/.
+test-all: test/gc/build/Release/binding.node ## Run everything in test/.
 	$(PYTHON) tools/test.py --mode=debug,release
 
-test-all-valgrind: test-build
+test-all-valgrind: build-addons
 	$(PYTHON) tools/test.py --mode=debug,release --valgrind
 
 CI_NATIVE_SUITES ?= addons addons-napi
@@ -417,7 +338,7 @@ CI_DOC := doctool
 # Build and test addons without building anything else
 # Related CI job: node-test-commit-arm-fanned
 test-ci-native: LOGLEVEL := info
-test-ci-native: | test/addons/.buildstamp test/addons-napi/.buildstamp
+test-ci-native: $(ADDON_FILES)
 	$(PYTHON) tools/test.py $(PARALLEL_ARGS) -p tap --logfile test.tap \
 		--mode=release --flaky-tests=$(FLAKY_TESTS) \
 		$(TEST_CI_ARGS) $(CI_NATIVE_SUITES)
@@ -439,7 +360,7 @@ test-ci-js: | clear-stalled
 .PHONY: test-ci
 # Related CI jobs: most CI tests, excluding node-test-commit-arm-fanned
 test-ci: LOGLEVEL := info
-test-ci: | clear-stalled build-addons build-addons-napi doc-only
+test-ci: build-addons | clear-stalled doc-only
 	out/Release/cctest --gtest_output=tap:cctest.tap
 	$(PYTHON) tools/test.py $(PARALLEL_ARGS) -p tap --logfile test.tap \
 		--mode=release --flaky-tests=$(FLAKY_TESTS) \
@@ -468,13 +389,13 @@ build-ci:
 run-ci: build-ci
 	$(MAKE) test-ci
 
-test-release: test-build
+test-release: build-addons
 	$(PYTHON) tools/test.py --mode=release
 
-test-debug: test-build
+test-debug: build-addons
 	$(PYTHON) tools/test.py --mode=debug
 
-test-message: test-build
+test-message: all
 	$(PYTHON) tools/test.py message
 
 test-simple: | cctest  # Depends on 'all'.
@@ -514,23 +435,21 @@ test-npm-publish: $(NODE_EXE)
 	npm_package_config_publishtest=true $(NODE) deps/npm/test/run.js
 
 .PHONY: test-addons-napi
-test-addons-napi: test-build-addons-napi
+test-addons-napi: build-addons
 	$(PYTHON) tools/test.py --mode=release addons-napi
 
 .PHONY: test-addons-napi-clean
 test-addons-napi-clean:
 	$(RM) -r test/addons-napi/*/build
-	$(RM) test/addons-napi/.buildstamp
 
 .PHONY: test-addons
-test-addons: test-build test-addons-napi
+test-addons: build-addons
 	$(PYTHON) tools/test.py --mode=release addons
 
 .PHONY: test-addons-clean
 test-addons-clean:
 	$(RM) -r test/addons/??_*/
 	$(RM) -r test/addons/*/build
-	$(RM) test/addons/.buildstamp test/addons/.docbuildstamp
 	$(MAKE) test-addons-napi-clean
 
 test-timers:
@@ -543,9 +462,7 @@ test-timers-clean:
 test-async-hooks:
 	$(PYTHON) tools/test.py --mode=release async-hooks
 
-test-with-async-hooks:
-	$(MAKE) build-addons
-	$(MAKE) build-addons-napi
+test-with-async-hooks: build-addons
 	$(MAKE) cctest
 	NODE_TEST_WITH_ASYNC_HOOKS=1 $(PYTHON) tools/test.py --mode=release -J \
 		$(CI_JS_SUITES) \
@@ -1113,36 +1030,32 @@ lint-js-ci:
 jslint-ci: lint-js-ci
 	@echo "Please use lint-js-ci instead of jslint-ci"
 
-LINT_CPP_ADDON_DOC_FILES = $(wildcard test/addons/??_*/*.cc test/addons/??_*/*.h)
 LINT_CPP_EXCLUDE ?=
 LINT_CPP_EXCLUDE += src/node_root_certs.h
-LINT_CPP_EXCLUDE += $(LINT_CPP_ADDON_DOC_FILES)
-LINT_CPP_EXCLUDE += $(wildcard test/addons-napi/??_*/*.cc test/addons-napi/??_*/*.h)
 # These files were copied more or less verbatim from V8.
 LINT_CPP_EXCLUDE += src/tracing/trace_event.h src/tracing/trace_event_common.h
 
-LINT_CPP_FILES = $(filter-out $(LINT_CPP_EXCLUDE), $(wildcard \
-	benchmark/misc/function_call/binding.cc \
-	src/*.c \
-	src/*.cc \
-	src/*.h \
-	src/*/*.c \
-	src/*/*.cc \
-	src/*/*.h \
-	test/addons/*/*.cc \
-	test/addons/*/*.h \
-	test/cctest/*.cc \
-	test/cctest/*.h \
-	test/addons-napi/*/*.cc \
-	test/addons-napi/*/*.h \
-	test/gc/binding.cc \
-	tools/icu/*.cc \
-	tools/icu/*.h \
-	))
-
-# Code blocks don't have newline at the end,
-# and the actual filename is generated so it won't match header guards
-ADDON_DOC_LINT_FLAGS=-whitespace/ending_newline,-build/header_guard
+LINT_CPP_FILES := \
+	$(filter-out \
+		$(LINT_CPP_EXCLUDE), \
+		$(wildcard \
+			benchmark/misc/function_call/binding.cc \
+			src/*.c \
+			src/*.cc \
+			src/*.h \
+			src/*/*.c \
+			src/*/*.cc \
+			src/*/*.h \
+			test/addons/*/*.cc \
+			test/addons/*/*.h \
+			test/cctest/*.cc \
+			test/cctest/*.h \
+			test/addons-napi/*/*.cc \
+			test/addons-napi/*/*.h \
+			test/gc/binding.cc \
+			tools/icu/*.cc \
+			tools/icu/*.h \
+			))
 
 .PHONY: lint-cpp
 # Lints the C++ code with cpplint.py and check-imports.py.
@@ -1154,10 +1067,6 @@ tools/.cpplintstamp: $(LINT_CPP_FILES)
 	@$(PYTHON) tools/check-imports.py
 	@touch $@
 
-lint-addon-docs: test/addons/.docbuildstamp
-	@echo "Running C++ linter on addon docs..."
-	@$(PYTHON) tools/cpplint.py --filter=$(ADDON_DOC_LINT_FLAGS) $(LINT_CPP_ADDON_DOC_FILES)
-
 cpplint: lint-cpp
 	@echo "Please use lint-cpp instead of cpplint"
 
@@ -1168,12 +1077,11 @@ lint: ## Run JS, C++, MD and doc linters.
 	@EXIT_STATUS=0 ; \
 	$(MAKE) lint-js || EXIT_STATUS=$$? ; \
 	$(MAKE) lint-cpp || EXIT_STATUS=$$? ; \
-	$(MAKE) lint-addon-docs || EXIT_STATUS=$$? ; \
 	exit $$EXIT_STATUS
 CONFLICT_RE=^>>>>>>> [0-9A-Fa-f]+|^<<<<<<< [A-Za-z]+
 
 # Related CI job: node-test-linter
-lint-ci: lint-js-ci lint-cpp lint-md lint-addon-docs
+lint-ci: lint-js-ci lint-cpp lint-md
 	@if ! ( grep -IEqrs "$(CONFLICT_RE)" benchmark deps doc lib src test tools ) \
 		&& ! ( find . -maxdepth 1 -type f | xargs grep -IEqs "$(CONFLICT_RE)" ); then \
 		exit 0 ; \
diff --git a/doc/api/addons.md b/doc/api/addons.md
index e2df5f30e9a32a..6b847237918ae4 100644
--- a/doc/api/addons.md
+++ b/doc/api/addons.md
@@ -1,3 +1,6 @@
+<!--
+Run `node tools/doc/addon-verify.js` when you change examples in this document.
+-->
 # C++ Addons
 
 <!--introduced_in=v0.10.0-->
@@ -94,12 +97,12 @@ The `module_name` must match the filename of the final binary (excluding
 the .node suffix).
 
 In the `hello.cc` example, then, the initialization function is `init` and the
-Addon module name is `addon`.
+Addon module name is `binding`.
 
 ### Building
 
 Once the source code has been written, it must be compiled into the binary
-`addon.node` file. To do so, create a file called `binding.gyp` in the
+`binding.node` file. To do so, create a file called `binding.gyp` in the
 top-level of the project describing the build configuration of the module
 using a JSON-like format. This file is used by [node-gyp][] -- a tool written
 specifically to compile Node.js Addons.
@@ -108,7 +111,7 @@ specifically to compile Node.js Addons.
 {
   "targets": [
     {
-      "target_name": "addon",
+      "target_name": "binding",
       "sources": [ "hello.cc" ]
     }
   ]
@@ -128,21 +131,21 @@ generate the appropriate project build files for the current platform. This
 will generate either a `Makefile` (on Unix platforms) or a `vcxproj` file
 (on Windows) in the `build/` directory.
 
-Next, invoke the `node-gyp build` command to generate the compiled `addon.node`
-file. This will be put into the `build/Release/` directory.
+Next, invoke the `node-gyp build` command to generate the compiled
+`binding.node` file. This will be put into the `build/Release/` directory.
 
 When using `npm install` to install a Node.js Addon, npm uses its own bundled
 version of `node-gyp` to perform this same set of actions, generating a
 compiled version of the Addon for the user's platform on demand.
 
 Once built, the binary Addon can be used from within Node.js by pointing
-[`require()`][require] to the built `addon.node` module:
+[`require()`][require] to the built `binding.node` module:
 
 ```js
 // hello.js
-const addon = require('./build/Release/addon');
+const binding = require('./build/Release/binding');
 
-console.log(addon.hello());
+console.log(binding.hello());
 // Prints: 'world'
 ```
 
@@ -159,9 +162,9 @@ similar to:
 
 ```js
 try {
-  return require('./build/Release/addon.node');
+  return require('./build/Release/binding.node');
 } catch (err) {
-  return require('./build/Debug/addon.node');
+  return require('./build/Debug/binding.node');
 }
 ```
 
@@ -195,9 +198,9 @@ When calling [`require()`][require], the `.node` extension can usually be
 omitted and Node.js will still find and initialize the Addon. One caveat,
 however, is that Node.js will first attempt to locate and load modules or
 JavaScript files that happen to share the same base name. For instance, if
-there is a file `addon.js` in the same directory as the binary `addon.node`,
-then [`require('addon')`][require] will give precedence to the `addon.js` file
-and load it instead.
+there is a file `binding.js` in the same directory as the binary `binding.node`,
+then [`require('binding')`][require] will give precedence to the `binding.js`
+file and load it instead.
 
 ## Native Abstractions for Node.js
 
@@ -284,8 +287,8 @@ Each of these examples using the following `binding.gyp` file:
 {
   "targets": [
     {
-      "target_name": "addon",
-      "sources": [ "addon.cc" ]
+      "target_name": "binding",
+      "sources": [ "binding.cc" ]
     }
   ]
 }
@@ -295,7 +298,7 @@ In cases where there is more than one `.cc` file, simply add the additional
 filename to the `sources` array. For example:
 
 ```json
-"sources": ["addon.cc", "myexample.cc"]
+"sources": ["binding.cc", "myexample.cc"]
 ```
 
 Once the `binding.gyp` file is ready, the example Addons can be configured and
@@ -317,7 +320,7 @@ The following example illustrates how to read function arguments passed from
 JavaScript and how to return a result:
 
 ```cpp
-// addon.cc
+// binding.cc
 #include <node.h>
 
 namespace demo {
@@ -374,9 +377,9 @@ Once compiled, the example Addon can be required and used from within Node.js:
 
 ```js
 // test.js
-const addon = require('./build/Release/addon');
+const binding = require('./build/Release/binding');
 
-console.log('This should be eight:', addon.add(3, 5));
+console.log('This should be eight:', binding.add(3, 5));
 ```
 
 
@@ -387,7 +390,7 @@ function and execute them from there. The following example illustrates how
 to invoke such callbacks:
 
 ```cpp
-// addon.cc
+// binding.cc
 #include <node.h>
 
 namespace demo {
@@ -427,9 +430,9 @@ To test it, run the following JavaScript:
 
 ```js
 // test.js
-const addon = require('./build/Release/addon');
+const binding = require('./build/Release/binding');
 
-addon((msg) => {
+binding((msg) => {
   console.log(msg);
 // Prints: 'hello world'
 });
@@ -444,7 +447,7 @@ illustrated in the following example. An object is created and returned with a
 property `msg` that echoes the string passed to `createObject()`:
 
 ```cpp
-// addon.cc
+// binding.cc
 #include <node.h>
 
 namespace demo {
@@ -478,10 +481,10 @@ To test it in JavaScript:
 
 ```js
 // test.js
-const addon = require('./build/Release/addon');
+const binding = require('./build/Release/binding');
 
-const obj1 = addon('hello');
-const obj2 = addon('world');
+const obj1 = binding('hello');
+const obj2 = binding('world');
 console.log(obj1.msg, obj2.msg);
 // Prints: 'hello world'
 ```
@@ -493,7 +496,7 @@ Another common scenario is creating JavaScript functions that wrap C++
 functions and returning those back to JavaScript:
 
 ```cpp
-// addon.cc
+// binding.cc
 #include <node.h>
 
 namespace demo {
@@ -537,9 +540,9 @@ To test:
 
 ```js
 // test.js
-const addon = require('./build/Release/addon');
+const binding = require('./build/Release/binding');
 
-const fn = addon();
+const fn = binding();
 console.log(fn());
 // Prints: 'hello world'
 ```
@@ -551,7 +554,7 @@ It is also possible to wrap C++ objects/classes in a way that allows new
 instances to be created using the JavaScript `new` operator:
 
 ```cpp
-// addon.cc
+// binding.cc
 #include <node.h>
 #include "myobject.h"
 
@@ -686,9 +689,9 @@ To build this example, the `myobject.cc` file must be added to the
 {
   "targets": [
     {
-      "target_name": "addon",
+      "target_name": "binding",
       "sources": [
-        "addon.cc",
+        "binding.cc",
         "myobject.cc"
       ]
     }
@@ -700,9 +703,9 @@ Test it with:
 
 ```js
 // test.js
-const addon = require('./build/Release/addon');
+const binding = require('./build/Release/binding');
 
-const obj = new addon.MyObject(10);
+const obj = new binding.MyObject(10);
 console.log(obj.plusOne());
 // Prints: 11
 console.log(obj.plusOne());
@@ -717,15 +720,15 @@ Alternatively, it is possible to use a factory pattern to avoid explicitly
 creating object instances using the JavaScript `new` operator:
 
 ```js
-const obj = addon.createObject();
+const obj = binding.createObject();
 // instead of:
-// const obj = new addon.Object();
+// const obj = new binding.Object();
 ```
 
-First, the `createObject()` method is implemented in `addon.cc`:
+First, the `createObject()` method is implemented in `binding.cc`:
 
 ```cpp
-// addon.cc
+// binding.cc
 #include <node.h>
 #include "myobject.h"
 
@@ -881,9 +884,9 @@ Once again, to build this example, the `myobject.cc` file must be added to the
 {
   "targets": [
     {
-      "target_name": "addon",
+      "target_name": "binding",
       "sources": [
-        "addon.cc",
+        "binding.cc",
         "myobject.cc"
       ]
     }
@@ -895,7 +898,7 @@ Test it with:
 
 ```js
 // test.js
-const createObject = require('./build/Release/addon');
+const createObject = require('./build/Release/binding');
 
 const obj = createObject(10);
 console.log(obj.plusOne());
@@ -923,7 +926,7 @@ wrapped objects around by unwrapping them with the Node.js helper function
 that can take two `MyObject` objects as input arguments:
 
 ```cpp
-// addon.cc
+// binding.cc
 #include <node.h>
 #include <node_object_wrap.h>
 #include "myobject.h"
@@ -1077,11 +1080,11 @@ Test it with:
 
 ```js
 // test.js
-const addon = require('./build/Release/addon');
+const binding = require('./build/Release/binding');
 
-const obj1 = addon.createObject(10);
-const obj2 = addon.createObject(20);
-const result = addon.add(obj1, obj2);
+const obj1 = binding.createObject(10);
+const obj2 = binding.createObject(20);
+const result = binding.add(obj1, obj2);
 
 console.log(result);
 // Prints: 30
@@ -1106,10 +1109,10 @@ and a pointer to untyped context data to be passed to that callback.
 
 Callbacks are run in last-in first-out order.
 
-The following `addon.cc` implements AtExit:
+The following `binding.cc` implements AtExit:
 
 ```cpp
-// addon.cc
+// binding.cc
 #include <assert.h>
 #include <stdlib.h>
 #include <node.h>
@@ -1161,7 +1164,7 @@ Test in JavaScript by running:
 
 ```js
 // test.js
-require('./build/Release/addon');
+require('./build/Release/binding');
 ```
 
 [Embedder's Guide]: https://github.com/v8/v8/wiki/Embedder's%20Guide
diff --git a/node.gyp b/node.gyp
index 6a4dfe0e434319..4d47bc37191a22 100644
--- a/node.gyp
+++ b/node.gyp
@@ -533,14 +533,6 @@
           #   node_dtrace_ustack.o    not supported on mac and linux
           #   node_dtrace_provider.o  All except OS X.  "dtrace -G" is not
           #                           used on OS X.
-          #
-          # Note that node_dtrace_provider.cc and node_dtrace_ustack.cc do not
-          # actually exist.  They're listed here to trick GYP into linking the
-          # corresponding object files into the final "node" executable.  These
-          # object files are generated by "dtrace -G" using custom actions
-          # below, and the GYP-generated Makefiles will properly build them when
-          # needed.
-          #
           'sources': [ 'src/node_dtrace.cc' ],
           'conditions': [
             [ 'OS=="linux"', {
@@ -550,8 +542,8 @@
             }],
             [ 'OS!="mac" and OS!="linux"', {
               'sources': [
-                'src/node_dtrace_ustack.cc',
-                'src/node_dtrace_provider.cc',
+                '<(OBJ_DIR)/node/src/node_dtrace_provider.o',
+                '<(OBJ_DIR)/node/src/node_dtrace_ustack.o',
               ]
             }
           ] ]
diff --git a/test/addons-napi/test_array/binding.gyp b/test/addons-napi/test_array/binding.gyp
index 44920de0986fdc..bb8f4b453aae1f 100644
--- a/test/addons-napi/test_array/binding.gyp
+++ b/test/addons-napi/test_array/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_array",
+      "target_name": "binding",
       "sources": [ "test_array.c" ]
     }
   ]
diff --git a/test/addons-napi/test_array/test.js b/test/addons-napi/test_array/test.js
index 75c181d9da8269..bba951d921d8ca 100644
--- a/test/addons-napi/test_array/test.js
+++ b/test/addons-napi/test_array/test.js
@@ -3,7 +3,7 @@ const common = require('../../common');
 const assert = require('assert');
 
 // Testing api calls for arrays
-const test_array = require(`./build/${common.buildType}/test_array`);
+const test_array = require(`./build/${common.buildType}/binding`);
 
 const array = [
   1,
diff --git a/test/addons-napi/test_async/binding.gyp b/test/addons-napi/test_async/binding.gyp
index cf8beb70c68e78..2837dfb294b776 100644
--- a/test/addons-napi/test_async/binding.gyp
+++ b/test/addons-napi/test_async/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_async",
+      "target_name": "binding",
       "sources": [ "test_async.cc" ]
     }
   ]
diff --git a/test/addons-napi/test_async/test-async-hooks.js b/test/addons-napi/test_async/test-async-hooks.js
index db6e0cbd1679e6..32c798d085821a 100644
--- a/test/addons-napi/test_async/test-async-hooks.js
+++ b/test/addons-napi/test_async/test-async-hooks.js
@@ -2,7 +2,7 @@
 const common = require('../../common');
 const assert = require('assert');
 const async_hooks = require('async_hooks');
-const test_async = require(`./build/${common.buildType}/test_async`);
+const test_async = require(`./build/${common.buildType}/binding`);
 
 const events = [];
 let testId;
diff --git a/test/addons-napi/test_async/test.js b/test/addons-napi/test_async/test.js
index 34ecae08e67f2e..1e5194abea74af 100644
--- a/test/addons-napi/test_async/test.js
+++ b/test/addons-napi/test_async/test.js
@@ -2,7 +2,7 @@
 const common = require('../../common');
 const assert = require('assert');
 const child_process = require('child_process');
-const test_async = require(`./build/${common.buildType}/test_async`);
+const test_async = require(`./build/${common.buildType}/binding`);
 
 const testException = 'test_async_cb_exception';
 
diff --git a/test/addons-napi/test_buffer/binding.gyp b/test/addons-napi/test_buffer/binding.gyp
index e41a3993cd7c9d..b3a6cee217f729 100644
--- a/test/addons-napi/test_buffer/binding.gyp
+++ b/test/addons-napi/test_buffer/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_buffer",
+      "target_name": "binding",
       "sources": [ "test_buffer.c" ]
     }
   ]
diff --git a/test/addons-napi/test_buffer/test.js b/test/addons-napi/test_buffer/test.js
index 740b0474a79c60..29104b906fe606 100644
--- a/test/addons-napi/test_buffer/test.js
+++ b/test/addons-napi/test_buffer/test.js
@@ -2,7 +2,7 @@
 // Flags: --expose-gc
 
 const common = require('../../common');
-const binding = require(`./build/${common.buildType}/test_buffer`);
+const binding = require(`./build/${common.buildType}/binding`);
 const assert = require('assert');
 
 assert.strictEqual(binding.newBuffer().toString(), binding.theText);
diff --git a/test/addons-napi/test_constructor/binding.gyp b/test/addons-napi/test_constructor/binding.gyp
index 1945a9fd5a711e..e194da9da2a09e 100644
--- a/test/addons-napi/test_constructor/binding.gyp
+++ b/test/addons-napi/test_constructor/binding.gyp
@@ -1,12 +1,8 @@
 {
   "targets": [
     {
-      "target_name": "test_constructor",
+      "target_name": "binding",
       "sources": [ "test_constructor.c" ]
-    },
-    {
-      "target_name": "test_constructor_name",
-      "sources": [ "test_constructor_name.c" ]
     }
   ]
 }
diff --git a/test/addons-napi/test_constructor/test.js b/test/addons-napi/test_constructor/test.js
index 616ba6c2a2927e..6fd124c8920b79 100644
--- a/test/addons-napi/test_constructor/test.js
+++ b/test/addons-napi/test_constructor/test.js
@@ -3,7 +3,7 @@ const common = require('../../common');
 const assert = require('assert');
 
 // Testing api calls for a constructor that defines properties
-const TestConstructor = require(`./build/${common.buildType}/test_constructor`);
+const TestConstructor = require(`./build/${common.buildType}/binding`);
 const test_object = new TestConstructor();
 
 assert.strictEqual(test_object.echo('hello'), 'hello');
diff --git a/test/addons-napi/test_constructor_name/binding.gyp b/test/addons-napi/test_constructor_name/binding.gyp
new file mode 100644
index 00000000000000..8beb20988872d8
--- /dev/null
+++ b/test/addons-napi/test_constructor_name/binding.gyp
@@ -0,0 +1,8 @@
+{
+  "targets": [
+    {
+      "target_name": "binding",
+      "sources": [ "test_constructor_name.c" ]
+    }
+  ]
+}
diff --git a/test/addons-napi/test_constructor/test2.js b/test/addons-napi/test_constructor_name/test.js
similarity index 69%
rename from test/addons-napi/test_constructor/test2.js
rename to test/addons-napi/test_constructor_name/test.js
index 64c03cbc684ac3..0f713ff55c7ca2 100644
--- a/test/addons-napi/test_constructor/test2.js
+++ b/test/addons-napi/test_constructor_name/test.js
@@ -3,6 +3,5 @@ const common = require('../../common');
 const assert = require('assert');
 
 // Testing api calls for a constructor that defines properties
-const TestConstructor =
-    require(`./build/${common.buildType}/test_constructor_name`);
+const TestConstructor = require(`./build/${common.buildType}/binding`);
 assert.strictEqual(TestConstructor.name, 'MyObject');
diff --git a/test/addons-napi/test_constructor/test_constructor_name.c b/test/addons-napi/test_constructor_name/test_constructor_name.c
similarity index 100%
rename from test/addons-napi/test_constructor/test_constructor_name.c
rename to test/addons-napi/test_constructor_name/test_constructor_name.c
diff --git a/test/addons-napi/test_conversions/binding.gyp b/test/addons-napi/test_conversions/binding.gyp
index 8d8d6fc0128294..b667ffd6de819b 100644
--- a/test/addons-napi/test_conversions/binding.gyp
+++ b/test/addons-napi/test_conversions/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_conversions",
+      "target_name": "binding",
       "sources": [ "test_conversions.c" ]
     }
   ]
diff --git a/test/addons-napi/test_conversions/test.js b/test/addons-napi/test_conversions/test.js
index 73d2c3314f600b..9604bb7381855f 100644
--- a/test/addons-napi/test_conversions/test.js
+++ b/test/addons-napi/test_conversions/test.js
@@ -1,7 +1,7 @@
 'use strict';
 const common = require('../../common');
 const assert = require('assert');
-const test = require(`./build/${common.buildType}/test_conversions`);
+const test = require(`./build/${common.buildType}/binding`);
 
 const boolExpected = /boolean was expected/;
 const numberExpected = /number was expected/;
diff --git a/test/addons-napi/test_dataview/binding.gyp b/test/addons-napi/test_dataview/binding.gyp
index bf014dc9e7b373..88a14ae8487566 100644
--- a/test/addons-napi/test_dataview/binding.gyp
+++ b/test/addons-napi/test_dataview/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_dataview",
+      "target_name": "binding",
       "sources": [ "test_dataview.c" ]
     }
   ]
diff --git a/test/addons-napi/test_dataview/test.js b/test/addons-napi/test_dataview/test.js
index a6be58494069e5..b24a7880357f1b 100644
--- a/test/addons-napi/test_dataview/test.js
+++ b/test/addons-napi/test_dataview/test.js
@@ -3,7 +3,7 @@ const common = require('../../common');
 const assert = require('assert');
 
 // Testing api calls for arrays
-const test_dataview = require(`./build/${common.buildType}/test_dataview`);
+const test_dataview = require(`./build/${common.buildType}/binding`);
 
 // Test for creating dataview
 {
diff --git a/test/addons-napi/test_env_sharing/binding.gyp b/test/addons-napi/test_env_sharing/binding.gyp
index 5699a8391dd347..8e38a63a1e383d 100644
--- a/test/addons-napi/test_env_sharing/binding.gyp
+++ b/test/addons-napi/test_env_sharing/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "store_env",
+      "target_name": "binding",
       "sources": [ "store_env.c" ]
     },
     {
diff --git a/test/addons-napi/test_env_sharing/test.js b/test/addons-napi/test_env_sharing/test.js
index 6e21bf4c638b80..edae60e1eb2865 100644
--- a/test/addons-napi/test_env_sharing/test.js
+++ b/test/addons-napi/test_env_sharing/test.js
@@ -1,7 +1,7 @@
 'use strict';
 
 const common = require('../../common');
-const storeEnv = require(`./build/${common.buildType}/store_env`);
+const storeEnv = require(`./build/${common.buildType}/binding`);
 const compareEnv = require(`./build/${common.buildType}/compare_env`);
 const assert = require('assert');
 
diff --git a/test/addons-napi/test_error/binding.gyp b/test/addons-napi/test_error/binding.gyp
index c2defd9551a31b..8e80d2bfa28fb9 100644
--- a/test/addons-napi/test_error/binding.gyp
+++ b/test/addons-napi/test_error/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_error",
+      "target_name": "binding",
       "sources": [ "test_error.cc" ]
     }
   ]
diff --git a/test/addons-napi/test_error/test.js b/test/addons-napi/test_error/test.js
index d5c92cb4c3cd2e..154294fca5550b 100644
--- a/test/addons-napi/test_error/test.js
+++ b/test/addons-napi/test_error/test.js
@@ -1,7 +1,7 @@
 'use strict';
 
 const common = require('../../common');
-const test_error = require(`./build/${common.buildType}/test_error`);
+const test_error = require(`./build/${common.buildType}/binding`);
 const assert = require('assert');
 const theError = new Error('Some error');
 const theTypeError = new TypeError('Some type error');
diff --git a/test/addons-napi/test_exception/binding.gyp b/test/addons-napi/test_exception/binding.gyp
index d2e4586e46bc1e..3a20bafabca773 100644
--- a/test/addons-napi/test_exception/binding.gyp
+++ b/test/addons-napi/test_exception/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_exception",
+      "target_name": "binding",
       "sources": [ "test_exception.c" ]
     }
   ]
diff --git a/test/addons-napi/test_exception/test.js b/test/addons-napi/test_exception/test.js
index 8bd2f50b12b15f..dc82249e6c0344 100644
--- a/test/addons-napi/test_exception/test.js
+++ b/test/addons-napi/test_exception/test.js
@@ -1,7 +1,7 @@
 'use strict';
 
 const common = require('../../common');
-const test_exception = require(`./build/${common.buildType}/test_exception`);
+const test_exception = require(`./build/${common.buildType}/binding`);
 const assert = require('assert');
 const theError = new Error('Some error');
 function throwTheError() {
diff --git a/test/addons-napi/test_fatal/binding.gyp b/test/addons-napi/test_fatal/binding.gyp
index ad661825f1fa9b..99c7e5de4c1657 100644
--- a/test/addons-napi/test_fatal/binding.gyp
+++ b/test/addons-napi/test_fatal/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_fatal",
+      "target_name": "binding",
       "sources": [ "test_fatal.c" ]
     }
   ]
diff --git a/test/addons-napi/test_fatal/test.js b/test/addons-napi/test_fatal/test.js
index 7ff9a395635dce..c8724d054b6100 100644
--- a/test/addons-napi/test_fatal/test.js
+++ b/test/addons-napi/test_fatal/test.js
@@ -2,7 +2,7 @@
 const common = require('../../common');
 const assert = require('assert');
 const child_process = require('child_process');
-const test_fatal = require(`./build/${common.buildType}/test_fatal`);
+const test_fatal = require(`./build/${common.buildType}/binding`);
 
 // Test in a child process because the test code will trigger a fatal error
 // that crashes the process.
diff --git a/test/addons-napi/test_fatal/test2.js b/test/addons-napi/test_fatal/test2.js
index b9bde8f13016cc..52a18deedd4694 100644
--- a/test/addons-napi/test_fatal/test2.js
+++ b/test/addons-napi/test_fatal/test2.js
@@ -2,7 +2,7 @@
 const common = require('../../common');
 const assert = require('assert');
 const child_process = require('child_process');
-const test_fatal = require(`./build/${common.buildType}/test_fatal`);
+const test_fatal = require(`./build/${common.buildType}/binding`);
 
 // Test in a child process because the test code will trigger a fatal error
 // that crashes the process.
diff --git a/test/addons-napi/test_function/binding.gyp b/test/addons-napi/test_function/binding.gyp
index 2b015bddd7d222..61035d9c6bddcf 100644
--- a/test/addons-napi/test_function/binding.gyp
+++ b/test/addons-napi/test_function/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_function",
+      "target_name": "binding",
       "sources": [ "test_function.c" ]
     }
   ]
diff --git a/test/addons-napi/test_function/test.js b/test/addons-napi/test_function/test.js
index 752e9965b23039..6f496796bd017c 100644
--- a/test/addons-napi/test_function/test.js
+++ b/test/addons-napi/test_function/test.js
@@ -3,7 +3,7 @@ const common = require('../../common');
 const assert = require('assert');
 
 // testing api calls for function
-const test_function = require(`./build/${common.buildType}/test_function`);
+const test_function = require(`./build/${common.buildType}/binding`);
 
 
 function func1() {
diff --git a/test/addons-napi/test_general/binding.gyp b/test/addons-napi/test_general/binding.gyp
index f8ef9f59613355..cf2a8574fe2fc2 100644
--- a/test/addons-napi/test_general/binding.gyp
+++ b/test/addons-napi/test_general/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_general",
+      "target_name": "binding",
       "sources": [ "test_general.c" ]
     }
   ]
diff --git a/test/addons-napi/test_general/test.js b/test/addons-napi/test_general/test.js
index ee6618c8121289..fba407d8dafba6 100644
--- a/test/addons-napi/test_general/test.js
+++ b/test/addons-napi/test_general/test.js
@@ -2,7 +2,7 @@
 // Flags: --expose-gc
 
 const common = require('../../common');
-const test_general = require(`./build/${common.buildType}/test_general`);
+const test_general = require(`./build/${common.buildType}/binding`);
 const assert = require('assert');
 
 const val1 = '1';
diff --git a/test/addons-napi/test_general/testGlobals.js b/test/addons-napi/test_general/testGlobals.js
index 38cf3f3edbccbc..ffde6de47cce91 100644
--- a/test/addons-napi/test_general/testGlobals.js
+++ b/test/addons-napi/test_general/testGlobals.js
@@ -2,7 +2,7 @@
 const common = require('../../common');
 const assert = require('assert');
 
-const test_globals = require(`./build/${common.buildType}/test_general`);
+const test_globals = require(`./build/${common.buildType}/binding`);
 
 assert.strictEqual(test_globals.getUndefined(), undefined);
 assert.strictEqual(test_globals.getNull(), null);
diff --git a/test/addons-napi/test_general/testInstanceOf.js b/test/addons-napi/test_general/testInstanceOf.js
index 3b37188ab4d20c..40e081b9aac2a2 100644
--- a/test/addons-napi/test_general/testInstanceOf.js
+++ b/test/addons-napi/test_general/testInstanceOf.js
@@ -6,7 +6,7 @@ const assert = require('assert');
 
 // addon is referenced through the eval expression in testFile
 // eslint-disable-next-line no-unused-vars
-const addon = require(`./build/${common.buildType}/test_general`);
+const addon = require(`./build/${common.buildType}/binding`);
 const path = require('path');
 
 // This test depends on a number of V8 tests.
diff --git a/test/addons-napi/test_general/testNapiRun.js b/test/addons-napi/test_general/testNapiRun.js
index af9f89fa29d53d..2425d5db524ed2 100644
--- a/test/addons-napi/test_general/testNapiRun.js
+++ b/test/addons-napi/test_general/testNapiRun.js
@@ -5,7 +5,7 @@ const assert = require('assert');
 
 // addon is referenced through the eval expression in testFile
 // eslint-disable-next-line no-unused-vars
-const addon = require(`./build/${common.buildType}/test_general`);
+const addon = require(`./build/${common.buildType}/binding`);
 
 const testCase = '(41.92 + 0.08);';
 const expected = 42;
diff --git a/test/addons-napi/test_general/testNapiStatus.js b/test/addons-napi/test_general/testNapiStatus.js
index a588862098f68f..e71bdc6892c842 100644
--- a/test/addons-napi/test_general/testNapiStatus.js
+++ b/test/addons-napi/test_general/testNapiStatus.js
@@ -1,7 +1,7 @@
 'use strict';
 
 const common = require('../../common');
-const addon = require(`./build/${common.buildType}/test_general`);
+const addon = require(`./build/${common.buildType}/binding`);
 const assert = require('assert');
 
 addon.createNapiError();
diff --git a/test/addons-napi/test_handle_scope/binding.gyp b/test/addons-napi/test_handle_scope/binding.gyp
index daa96681a52018..a063d28d81fb2e 100644
--- a/test/addons-napi/test_handle_scope/binding.gyp
+++ b/test/addons-napi/test_handle_scope/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_handle_scope",
+      "target_name": "binding",
       "sources": [ "test_handle_scope.c" ]
     }
   ]
diff --git a/test/addons-napi/test_handle_scope/test.js b/test/addons-napi/test_handle_scope/test.js
index 53abfe178c8d7d..eed99f81bd700b 100644
--- a/test/addons-napi/test_handle_scope/test.js
+++ b/test/addons-napi/test_handle_scope/test.js
@@ -3,8 +3,7 @@ const common = require('../../common');
 const assert = require('assert');
 
 // testing handle scope api calls
-const testHandleScope =
-    require(`./build/${common.buildType}/test_handle_scope`);
+const testHandleScope = require(`./build/${common.buildType}/binding`);
 
 testHandleScope.NewScope();
 
diff --git a/test/addons-napi/test_number/binding.gyp b/test/addons-napi/test_number/binding.gyp
index c934d5ef03bfc7..9a7b2a3d319c50 100644
--- a/test/addons-napi/test_number/binding.gyp
+++ b/test/addons-napi/test_number/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_number",
+      "target_name": "binding",
       "sources": [ "test_number.c" ]
     }
   ]
diff --git a/test/addons-napi/test_number/test.js b/test/addons-napi/test_number/test.js
index e92c6d0cd29951..18d39ec91feba8 100644
--- a/test/addons-napi/test_number/test.js
+++ b/test/addons-napi/test_number/test.js
@@ -1,7 +1,7 @@
 'use strict';
 const common = require('../../common');
 const assert = require('assert');
-const test_number = require(`./build/${common.buildType}/test_number`);
+const test_number = require(`./build/${common.buildType}/binding`);
 
 
 // testing api calls for number
diff --git a/test/addons-napi/test_object/binding.gyp b/test/addons-napi/test_object/binding.gyp
index be225ace779027..f589ab92fab429 100644
--- a/test/addons-napi/test_object/binding.gyp
+++ b/test/addons-napi/test_object/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_object",
+      "target_name": "binding",
       "sources": [ "test_object.c" ]
     }
   ]
diff --git a/test/addons-napi/test_object/test.js b/test/addons-napi/test_object/test.js
index 8e44a1b5ed647f..13defcc512a180 100644
--- a/test/addons-napi/test_object/test.js
+++ b/test/addons-napi/test_object/test.js
@@ -3,7 +3,7 @@ const common = require('../../common');
 const assert = require('assert');
 
 // Testing api calls for objects
-const test_object = require(`./build/${common.buildType}/test_object`);
+const test_object = require(`./build/${common.buildType}/binding`);
 
 
 const object = {
diff --git a/test/addons-napi/test_promise/binding.gyp b/test/addons-napi/test_promise/binding.gyp
index bf266f93db74be..f9a3f75744e98a 100644
--- a/test/addons-napi/test_promise/binding.gyp
+++ b/test/addons-napi/test_promise/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_promise",
+      "target_name": "binding",
       "sources": [ "test_promise.c" ]
     }
   ]
diff --git a/test/addons-napi/test_promise/test.js b/test/addons-napi/test_promise/test.js
index 6dc51b3fa558a2..c038c969951cf7 100644
--- a/test/addons-napi/test_promise/test.js
+++ b/test/addons-napi/test_promise/test.js
@@ -5,7 +5,7 @@ const common = require('../../common');
 // This tests the promise-related n-api calls
 
 const assert = require('assert');
-const test_promise = require(`./build/${common.buildType}/test_promise`);
+const test_promise = require(`./build/${common.buildType}/binding`);
 
 common.crashOnUnhandledRejection();
 
diff --git a/test/addons-napi/test_properties/binding.gyp b/test/addons-napi/test_properties/binding.gyp
index 345e5c88d7c6b9..c897a825031aa5 100644
--- a/test/addons-napi/test_properties/binding.gyp
+++ b/test/addons-napi/test_properties/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_properties",
+      "target_name": "binding",
       "sources": [ "test_properties.c" ]
     }
   ]
diff --git a/test/addons-napi/test_properties/test.js b/test/addons-napi/test_properties/test.js
index 13a546c7b87f81..9d7c9ba34148eb 100644
--- a/test/addons-napi/test_properties/test.js
+++ b/test/addons-napi/test_properties/test.js
@@ -5,7 +5,7 @@ const readonlyErrorRE =
   /^TypeError: Cannot assign to read only property '.*' of object '#<Object>'$/;
 
 // Testing api calls for defining properties
-const test_object = require(`./build/${common.buildType}/test_properties`);
+const test_object = require(`./build/${common.buildType}/binding`);
 
 assert.strictEqual(test_object.echo('hello'), 'hello');
 
diff --git a/test/addons-napi/test_reference/binding.gyp b/test/addons-napi/test_reference/binding.gyp
index 3a6d69a65c81f5..9a4583cb833535 100644
--- a/test/addons-napi/test_reference/binding.gyp
+++ b/test/addons-napi/test_reference/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_reference",
+      "target_name": "binding",
       "sources": [ "test_reference.c" ]
     }
   ]
diff --git a/test/addons-napi/test_reference/test.js b/test/addons-napi/test_reference/test.js
index 14932a74ca70b0..caaa9b599e8c70 100644
--- a/test/addons-napi/test_reference/test.js
+++ b/test/addons-napi/test_reference/test.js
@@ -4,7 +4,7 @@
 const common = require('../../common');
 const assert = require('assert');
 
-const test_reference = require(`./build/${common.buildType}/test_reference`);
+const test_reference = require(`./build/${common.buildType}/binding`);
 
 // This test script uses external values with finalizer callbacks
 // in order to track when values get garbage-collected. Each invocation
diff --git a/test/addons-napi/test_string/binding.gyp b/test/addons-napi/test_string/binding.gyp
index d4825de933985f..f49410e3dd8849 100644
--- a/test/addons-napi/test_string/binding.gyp
+++ b/test/addons-napi/test_string/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_string",
+      "target_name": "binding",
       "sources": [ "test_string.c" ]
     }
   ]
diff --git a/test/addons-napi/test_string/test.js b/test/addons-napi/test_string/test.js
index 5ce3d739c7a941..fd58ea0983be9d 100644
--- a/test/addons-napi/test_string/test.js
+++ b/test/addons-napi/test_string/test.js
@@ -3,7 +3,7 @@ const common = require('../../common');
 const assert = require('assert');
 
 // testing api calls for string
-const test_string = require(`./build/${common.buildType}/test_string`);
+const test_string = require(`./build/${common.buildType}/binding`);
 
 const empty = '';
 assert.strictEqual(test_string.TestLatin1(empty), empty);
diff --git a/test/addons-napi/test_symbol/binding.gyp b/test/addons-napi/test_symbol/binding.gyp
index 6ef3407968db94..bae042ee0e6af6 100644
--- a/test/addons-napi/test_symbol/binding.gyp
+++ b/test/addons-napi/test_symbol/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_symbol",
+      "target_name": "binding",
       "sources": [ "test_symbol.c" ]
     }
   ]
diff --git a/test/addons-napi/test_symbol/test1.js b/test/addons-napi/test_symbol/test1.js
index 25eb473c4b1b9d..83f2f71f6f80a4 100644
--- a/test/addons-napi/test_symbol/test1.js
+++ b/test/addons-napi/test_symbol/test1.js
@@ -3,7 +3,7 @@ const common = require('../../common');
 const assert = require('assert');
 
 // testing api calls for symbol
-const test_symbol = require(`./build/${common.buildType}/test_symbol`);
+const test_symbol = require(`./build/${common.buildType}/binding`);
 
 const sym = test_symbol.New('test');
 assert.strictEqual(sym.toString(), 'Symbol(test)');
diff --git a/test/addons-napi/test_symbol/test2.js b/test/addons-napi/test_symbol/test2.js
index 60512431110a5b..4ff0955c5dbe35 100644
--- a/test/addons-napi/test_symbol/test2.js
+++ b/test/addons-napi/test_symbol/test2.js
@@ -3,7 +3,7 @@ const common = require('../../common');
 const assert = require('assert');
 
 // testing api calls for symbol
-const test_symbol = require(`./build/${common.buildType}/test_symbol`);
+const test_symbol = require(`./build/${common.buildType}/binding`);
 
 const fooSym = test_symbol.New('foo');
 const myObj = {};
diff --git a/test/addons-napi/test_symbol/test3.js b/test/addons-napi/test_symbol/test3.js
index a7c6c18c025480..72379644cb0805 100644
--- a/test/addons-napi/test_symbol/test3.js
+++ b/test/addons-napi/test_symbol/test3.js
@@ -3,7 +3,7 @@ const common = require('../../common');
 const assert = require('assert');
 
 // testing api calls for symbol
-const test_symbol = require(`./build/${common.buildType}/test_symbol`);
+const test_symbol = require(`./build/${common.buildType}/binding`);
 
 assert.notStrictEqual(test_symbol.New(), test_symbol.New());
 assert.notStrictEqual(test_symbol.New('foo'), test_symbol.New('foo'));
diff --git a/test/addons-napi/test_typedarray/binding.gyp b/test/addons-napi/test_typedarray/binding.gyp
index 8b4a4dc622b2ef..29fb299d329eb9 100644
--- a/test/addons-napi/test_typedarray/binding.gyp
+++ b/test/addons-napi/test_typedarray/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_typedarray",
+      "target_name": "binding",
       "sources": [ "test_typedarray.c" ]
     }
   ]
diff --git a/test/addons-napi/test_typedarray/test.js b/test/addons-napi/test_typedarray/test.js
index ed37f3c5d4da1b..debf99b80aeeab 100644
--- a/test/addons-napi/test_typedarray/test.js
+++ b/test/addons-napi/test_typedarray/test.js
@@ -3,7 +3,7 @@ const common = require('../../common');
 const assert = require('assert');
 
 // Testing api calls for arrays
-const test_typedarray = require(`./build/${common.buildType}/test_typedarray`);
+const test_typedarray = require(`./build/${common.buildType}/binding`);
 
 const byteArray = new Uint8Array(3);
 byteArray[0] = 0;
diff --git a/test/addons-napi/test_uv_loop/binding.gyp b/test/addons-napi/test_uv_loop/binding.gyp
index 81fcfdc592a523..e403df92455852 100644
--- a/test/addons-napi/test_uv_loop/binding.gyp
+++ b/test/addons-napi/test_uv_loop/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_uv_loop",
+      "target_name": "binding",
       "sources": [ "test_uv_loop.cc" ]
     }
   ]
diff --git a/test/addons-napi/test_uv_loop/test.js b/test/addons-napi/test_uv_loop/test.js
index 4efc3c6fcd7001..ce13a482bdcedb 100644
--- a/test/addons-napi/test_uv_loop/test.js
+++ b/test/addons-napi/test_uv_loop/test.js
@@ -1,5 +1,5 @@
 'use strict';
 const common = require('../../common');
-const { SetImmediate } = require(`./build/${common.buildType}/test_uv_loop`);
+const { SetImmediate } = require(`./build/${common.buildType}/binding`);
 
 SetImmediate(common.mustCall());
diff --git a/test/addons-napi/test_warning/binding.gyp b/test/addons-napi/test_warning/binding.gyp
index a44593e2518c24..d84028f085db15 100644
--- a/test/addons-napi/test_warning/binding.gyp
+++ b/test/addons-napi/test_warning/binding.gyp
@@ -1,7 +1,7 @@
 {
   "targets": [
     {
-      "target_name": "test_warning",
+      "target_name": "binding",
       "sources": [ "test_warning.c" ]
     },
     {
diff --git a/test/addons-napi/test_warning/test.js b/test/addons-napi/test_warning/test.js
index c82008435fd8fd..6909a1ebcddf21 100644
--- a/test/addons-napi/test_warning/test.js
+++ b/test/addons-napi/test_warning/test.js
@@ -2,7 +2,7 @@
 
 if (process.argv[2] === 'child') {
   const common = require('../../common');
-  console.log(require(`./build/${common.buildType}/test_warning`));
+  console.log(require(`./build/${common.buildType}/binding`));
   console.log(require(`./build/${common.buildType}/test_warning2`));
 } else {
   const run = require('child_process').spawnSync;
diff --git a/test/addons/.gitignore b/test/addons/.gitignore
index bde1cf3ab9662b..ff6b007943190d 100644
--- a/test/addons/.gitignore
+++ b/test/addons/.gitignore
@@ -1,5 +1,3 @@
-.buildstamp
-.docbuildstamp
 Makefile
 *.Makefile
 *.mk
diff --git a/test/addons/01_function_arguments/binding.cc b/test/addons/01_function_arguments/binding.cc
new file mode 100644
index 00000000000000..1379699453d8ed
--- /dev/null
+++ b/test/addons/01_function_arguments/binding.cc
@@ -0,0 +1,52 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// binding.cc
+#include <node.h>
+
+namespace demo {
+
+using v8::Exception;
+using v8::FunctionCallbackInfo;
+using v8::Isolate;
+using v8::Local;
+using v8::Number;
+using v8::Object;
+using v8::String;
+using v8::Value;
+
+// This is the implementation of the "add" method
+// Input arguments are passed using the
+// const FunctionCallbackInfo<Value>& args struct
+void Add(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = args.GetIsolate();
+
+  // Check the number of arguments passed.
+  if (args.Length() < 2) {
+    // Throw an Error that is passed back to JavaScript
+    isolate->ThrowException(Exception::TypeError(
+        String::NewFromUtf8(isolate, "Wrong number of arguments")));
+    return;
+  }
+
+  // Check the argument types
+  if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
+    isolate->ThrowException(Exception::TypeError(
+        String::NewFromUtf8(isolate, "Wrong arguments")));
+    return;
+  }
+
+  // Perform the operation
+  double value = args[0]->NumberValue() + args[1]->NumberValue();
+  Local<Number> num = Number::New(isolate, value);
+
+  // Set the return value (using the passed in
+  // FunctionCallbackInfo<Value>&)
+  args.GetReturnValue().Set(num);
+}
+
+void Init(Local<Object> exports) {
+  NODE_SET_METHOD(exports, "add", Add);
+}
+
+NODE_MODULE(NODE_GYP_MODULE_NAME, Init)
+
+}  // namespace demo
diff --git a/test/addons/01_function_arguments/binding.gyp b/test/addons/01_function_arguments/binding.gyp
new file mode 100644
index 00000000000000..6cd05e5bba3ccd
--- /dev/null
+++ b/test/addons/01_function_arguments/binding.gyp
@@ -0,0 +1,2 @@
+# Auto-generated by `node tools/doc/addon-verify.js`
+{"targets":[{"target_name":"binding","defines":["V8_DEPRECATION_WARNINGS=1"],"sources":["binding.cc","test.js"]}]}
\ No newline at end of file
diff --git a/test/addons/01_function_arguments/test.js b/test/addons/01_function_arguments/test.js
new file mode 100644
index 00000000000000..3372ff3329ca22
--- /dev/null
+++ b/test/addons/01_function_arguments/test.js
@@ -0,0 +1,7 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+'use strict';
+const common = require('../../common');
+// test.js
+const binding = require(`./build/${common.buildType}/binding`);
+
+console.log('This should be eight:', binding.add(3, 5));
diff --git a/test/addons/02_callbacks/binding.cc b/test/addons/02_callbacks/binding.cc
new file mode 100644
index 00000000000000..53996f648bc195
--- /dev/null
+++ b/test/addons/02_callbacks/binding.cc
@@ -0,0 +1,30 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// binding.cc
+#include <node.h>
+
+namespace demo {
+
+using v8::Function;
+using v8::FunctionCallbackInfo;
+using v8::Isolate;
+using v8::Local;
+using v8::Null;
+using v8::Object;
+using v8::String;
+using v8::Value;
+
+void RunCallback(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = args.GetIsolate();
+  Local<Function> cb = Local<Function>::Cast(args[0]);
+  const unsigned argc = 1;
+  Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "hello world") };
+  cb->Call(Null(isolate), argc, argv);
+}
+
+void Init(Local<Object> exports, Local<Object> module) {
+  NODE_SET_METHOD(module, "exports", RunCallback);
+}
+
+NODE_MODULE(NODE_GYP_MODULE_NAME, Init)
+
+}  // namespace demo
diff --git a/test/addons/02_callbacks/binding.gyp b/test/addons/02_callbacks/binding.gyp
new file mode 100644
index 00000000000000..6cd05e5bba3ccd
--- /dev/null
+++ b/test/addons/02_callbacks/binding.gyp
@@ -0,0 +1,2 @@
+# Auto-generated by `node tools/doc/addon-verify.js`
+{"targets":[{"target_name":"binding","defines":["V8_DEPRECATION_WARNINGS=1"],"sources":["binding.cc","test.js"]}]}
\ No newline at end of file
diff --git a/test/addons/02_callbacks/test.js b/test/addons/02_callbacks/test.js
new file mode 100644
index 00000000000000..e18c44e269e677
--- /dev/null
+++ b/test/addons/02_callbacks/test.js
@@ -0,0 +1,10 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+'use strict';
+const common = require('../../common');
+// test.js
+const binding = require(`./build/${common.buildType}/binding`);
+
+binding((msg) => {
+  console.log(msg);
+// Prints: 'hello world'
+});
diff --git a/test/addons/03_object_factory/binding.cc b/test/addons/03_object_factory/binding.cc
new file mode 100644
index 00000000000000..d727552cd7128f
--- /dev/null
+++ b/test/addons/03_object_factory/binding.cc
@@ -0,0 +1,29 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// binding.cc
+#include <node.h>
+
+namespace demo {
+
+using v8::FunctionCallbackInfo;
+using v8::Isolate;
+using v8::Local;
+using v8::Object;
+using v8::String;
+using v8::Value;
+
+void CreateObject(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = args.GetIsolate();
+
+  Local<Object> obj = Object::New(isolate);
+  obj->Set(String::NewFromUtf8(isolate, "msg"), args[0]->ToString());
+
+  args.GetReturnValue().Set(obj);
+}
+
+void Init(Local<Object> exports, Local<Object> module) {
+  NODE_SET_METHOD(module, "exports", CreateObject);
+}
+
+NODE_MODULE(NODE_GYP_MODULE_NAME, Init)
+
+}  // namespace demo
diff --git a/test/addons/03_object_factory/binding.gyp b/test/addons/03_object_factory/binding.gyp
new file mode 100644
index 00000000000000..6cd05e5bba3ccd
--- /dev/null
+++ b/test/addons/03_object_factory/binding.gyp
@@ -0,0 +1,2 @@
+# Auto-generated by `node tools/doc/addon-verify.js`
+{"targets":[{"target_name":"binding","defines":["V8_DEPRECATION_WARNINGS=1"],"sources":["binding.cc","test.js"]}]}
\ No newline at end of file
diff --git a/test/addons/03_object_factory/test.js b/test/addons/03_object_factory/test.js
new file mode 100644
index 00000000000000..abe0ebb5254c3d
--- /dev/null
+++ b/test/addons/03_object_factory/test.js
@@ -0,0 +1,10 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+'use strict';
+const common = require('../../common');
+// test.js
+const binding = require(`./build/${common.buildType}/binding`);
+
+const obj1 = binding('hello');
+const obj2 = binding('world');
+console.log(obj1.msg, obj2.msg);
+// Prints: 'hello world'
diff --git a/test/addons/04_function_factory/binding.cc b/test/addons/04_function_factory/binding.cc
new file mode 100644
index 00000000000000..4891aa6b7b33df
--- /dev/null
+++ b/test/addons/04_function_factory/binding.cc
@@ -0,0 +1,39 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// binding.cc
+#include <node.h>
+
+namespace demo {
+
+using v8::Function;
+using v8::FunctionCallbackInfo;
+using v8::FunctionTemplate;
+using v8::Isolate;
+using v8::Local;
+using v8::Object;
+using v8::String;
+using v8::Value;
+
+void MyFunction(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = args.GetIsolate();
+  args.GetReturnValue().Set(String::NewFromUtf8(isolate, "hello world"));
+}
+
+void CreateFunction(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = args.GetIsolate();
+
+  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);
+  Local<Function> fn = tpl->GetFunction();
+
+  // omit this to make it anonymous
+  fn->SetName(String::NewFromUtf8(isolate, "theFunction"));
+
+  args.GetReturnValue().Set(fn);
+}
+
+void Init(Local<Object> exports, Local<Object> module) {
+  NODE_SET_METHOD(module, "exports", CreateFunction);
+}
+
+NODE_MODULE(NODE_GYP_MODULE_NAME, Init)
+
+}  // namespace demo
diff --git a/test/addons/04_function_factory/binding.gyp b/test/addons/04_function_factory/binding.gyp
new file mode 100644
index 00000000000000..6cd05e5bba3ccd
--- /dev/null
+++ b/test/addons/04_function_factory/binding.gyp
@@ -0,0 +1,2 @@
+# Auto-generated by `node tools/doc/addon-verify.js`
+{"targets":[{"target_name":"binding","defines":["V8_DEPRECATION_WARNINGS=1"],"sources":["binding.cc","test.js"]}]}
\ No newline at end of file
diff --git a/test/addons/04_function_factory/test.js b/test/addons/04_function_factory/test.js
new file mode 100644
index 00000000000000..71e726806910ec
--- /dev/null
+++ b/test/addons/04_function_factory/test.js
@@ -0,0 +1,9 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+'use strict';
+const common = require('../../common');
+// test.js
+const binding = require(`./build/${common.buildType}/binding`);
+
+const fn = binding();
+console.log(fn());
+// Prints: 'hello world'
diff --git a/test/addons/05_wrapping_c_objects/binding.cc b/test/addons/05_wrapping_c_objects/binding.cc
new file mode 100644
index 00000000000000..93609a4ecb7636
--- /dev/null
+++ b/test/addons/05_wrapping_c_objects/binding.cc
@@ -0,0 +1,17 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// binding.cc
+#include <node.h>
+#include "myobject.h"
+
+namespace demo {
+
+using v8::Local;
+using v8::Object;
+
+void InitAll(Local<Object> exports) {
+  MyObject::Init(exports);
+}
+
+NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)
+
+}  // namespace demo
diff --git a/test/addons/05_wrapping_c_objects/binding.gyp b/test/addons/05_wrapping_c_objects/binding.gyp
new file mode 100644
index 00000000000000..5d967d0948c39b
--- /dev/null
+++ b/test/addons/05_wrapping_c_objects/binding.gyp
@@ -0,0 +1,2 @@
+# Auto-generated by `node tools/doc/addon-verify.js`
+{"targets":[{"target_name":"binding","defines":["V8_DEPRECATION_WARNINGS=1"],"sources":["binding.cc","myobject.h","myobject.cc","test.js"]}]}
\ No newline at end of file
diff --git a/test/addons/05_wrapping_c_objects/myobject.cc b/test/addons/05_wrapping_c_objects/myobject.cc
new file mode 100644
index 00000000000000..4383ec44edffdd
--- /dev/null
+++ b/test/addons/05_wrapping_c_objects/myobject.cc
@@ -0,0 +1,73 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// myobject.cc
+#include "myobject.h"
+
+namespace demo {
+
+using v8::Context;
+using v8::Function;
+using v8::FunctionCallbackInfo;
+using v8::FunctionTemplate;
+using v8::Isolate;
+using v8::Local;
+using v8::Number;
+using v8::Object;
+using v8::Persistent;
+using v8::String;
+using v8::Value;
+
+Persistent<Function> MyObject::constructor;
+
+MyObject::MyObject(double value) : value_(value) {
+}
+
+MyObject::~MyObject() {
+}
+
+void MyObject::Init(Local<Object> exports) {
+  Isolate* isolate = exports->GetIsolate();
+
+  // Prepare constructor template
+  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
+  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
+  tpl->InstanceTemplate()->SetInternalFieldCount(1);
+
+  // Prototype
+  NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);
+
+  constructor.Reset(isolate, tpl->GetFunction());
+  exports->Set(String::NewFromUtf8(isolate, "MyObject"),
+               tpl->GetFunction());
+}
+
+void MyObject::New(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = args.GetIsolate();
+
+  if (args.IsConstructCall()) {
+    // Invoked as constructor: `new MyObject(...)`
+    double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
+    MyObject* obj = new MyObject(value);
+    obj->Wrap(args.This());
+    args.GetReturnValue().Set(args.This());
+  } else {
+    // Invoked as plain function `MyObject(...)`, turn into construct call.
+    const int argc = 1;
+    Local<Value> argv[argc] = { args[0] };
+    Local<Context> context = isolate->GetCurrentContext();
+    Local<Function> cons = Local<Function>::New(isolate, constructor);
+    Local<Object> result =
+        cons->NewInstance(context, argc, argv).ToLocalChecked();
+    args.GetReturnValue().Set(result);
+  }
+}
+
+void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = args.GetIsolate();
+
+  MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
+  obj->value_ += 1;
+
+  args.GetReturnValue().Set(Number::New(isolate, obj->value_));
+}
+
+}  // namespace demo
diff --git a/test/addons/05_wrapping_c_objects/myobject.h b/test/addons/05_wrapping_c_objects/myobject.h
new file mode 100644
index 00000000000000..9dcff129f46f4b
--- /dev/null
+++ b/test/addons/05_wrapping_c_objects/myobject.h
@@ -0,0 +1,27 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// myobject.h
+#ifndef MYOBJECT_H  // NOLINT(build/header_guard)
+#define MYOBJECT_H  // NOLINT(build/header_guard)
+
+#include <node.h>
+#include <node_object_wrap.h>
+
+namespace demo {
+
+class MyObject : public node::ObjectWrap {
+ public:
+  static void Init(v8::Local<v8::Object> exports);
+
+ private:
+  explicit MyObject(double value = 0);
+  ~MyObject();
+
+  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
+  static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
+  static v8::Persistent<v8::Function> constructor;
+  double value_;
+};
+
+}  // namespace demo
+
+#endif  // NOLINT(build/header_guard)
diff --git a/test/addons/05_wrapping_c_objects/test.js b/test/addons/05_wrapping_c_objects/test.js
new file mode 100644
index 00000000000000..b8d903798852c3
--- /dev/null
+++ b/test/addons/05_wrapping_c_objects/test.js
@@ -0,0 +1,13 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+'use strict';
+const common = require('../../common');
+// test.js
+const binding = require(`./build/${common.buildType}/binding`);
+
+const obj = new binding.MyObject(10);
+console.log(obj.plusOne());
+// Prints: 11
+console.log(obj.plusOne());
+// Prints: 12
+console.log(obj.plusOne());
+// Prints: 13
diff --git a/test/addons/06_factory_of_wrapped_objects/binding.cc b/test/addons/06_factory_of_wrapped_objects/binding.cc
new file mode 100644
index 00000000000000..71bf45ca6e6610
--- /dev/null
+++ b/test/addons/06_factory_of_wrapped_objects/binding.cc
@@ -0,0 +1,27 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// binding.cc
+#include <node.h>
+#include "myobject.h"
+
+namespace demo {
+
+using v8::FunctionCallbackInfo;
+using v8::Isolate;
+using v8::Local;
+using v8::Object;
+using v8::String;
+using v8::Value;
+
+void CreateObject(const FunctionCallbackInfo<Value>& args) {
+  MyObject::NewInstance(args);
+}
+
+void InitAll(Local<Object> exports, Local<Object> module) {
+  MyObject::Init(exports->GetIsolate());
+
+  NODE_SET_METHOD(module, "exports", CreateObject);
+}
+
+NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)
+
+}  // namespace demo
diff --git a/test/addons/06_factory_of_wrapped_objects/binding.gyp b/test/addons/06_factory_of_wrapped_objects/binding.gyp
new file mode 100644
index 00000000000000..5d967d0948c39b
--- /dev/null
+++ b/test/addons/06_factory_of_wrapped_objects/binding.gyp
@@ -0,0 +1,2 @@
+# Auto-generated by `node tools/doc/addon-verify.js`
+{"targets":[{"target_name":"binding","defines":["V8_DEPRECATION_WARNINGS=1"],"sources":["binding.cc","myobject.h","myobject.cc","test.js"]}]}
\ No newline at end of file
diff --git a/test/addons/06_factory_of_wrapped_objects/myobject.cc b/test/addons/06_factory_of_wrapped_objects/myobject.cc
new file mode 100644
index 00000000000000..c1286f42a34991
--- /dev/null
+++ b/test/addons/06_factory_of_wrapped_objects/myobject.cc
@@ -0,0 +1,83 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// myobject.cc
+#include <node.h>
+#include "myobject.h"
+
+namespace demo {
+
+using v8::Context;
+using v8::Function;
+using v8::FunctionCallbackInfo;
+using v8::FunctionTemplate;
+using v8::Isolate;
+using v8::Local;
+using v8::Number;
+using v8::Object;
+using v8::Persistent;
+using v8::String;
+using v8::Value;
+
+Persistent<Function> MyObject::constructor;
+
+MyObject::MyObject(double value) : value_(value) {
+}
+
+MyObject::~MyObject() {
+}
+
+void MyObject::Init(Isolate* isolate) {
+  // Prepare constructor template
+  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
+  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
+  tpl->InstanceTemplate()->SetInternalFieldCount(1);
+
+  // Prototype
+  NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);
+
+  constructor.Reset(isolate, tpl->GetFunction());
+}
+
+void MyObject::New(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = args.GetIsolate();
+
+  if (args.IsConstructCall()) {
+    // Invoked as constructor: `new MyObject(...)`
+    double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
+    MyObject* obj = new MyObject(value);
+    obj->Wrap(args.This());
+    args.GetReturnValue().Set(args.This());
+  } else {
+    // Invoked as plain function `MyObject(...)`, turn into construct call.
+    const int argc = 1;
+    Local<Value> argv[argc] = { args[0] };
+    Local<Function> cons = Local<Function>::New(isolate, constructor);
+    Local<Context> context = isolate->GetCurrentContext();
+    Local<Object> instance =
+        cons->NewInstance(context, argc, argv).ToLocalChecked();
+    args.GetReturnValue().Set(instance);
+  }
+}
+
+void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = args.GetIsolate();
+
+  const unsigned argc = 1;
+  Local<Value> argv[argc] = { args[0] };
+  Local<Function> cons = Local<Function>::New(isolate, constructor);
+  Local<Context> context = isolate->GetCurrentContext();
+  Local<Object> instance =
+      cons->NewInstance(context, argc, argv).ToLocalChecked();
+
+  args.GetReturnValue().Set(instance);
+}
+
+void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = args.GetIsolate();
+
+  MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
+  obj->value_ += 1;
+
+  args.GetReturnValue().Set(Number::New(isolate, obj->value_));
+}
+
+}  // namespace demo
diff --git a/test/addons/06_factory_of_wrapped_objects/myobject.h b/test/addons/06_factory_of_wrapped_objects/myobject.h
new file mode 100644
index 00000000000000..323c7a8739b00a
--- /dev/null
+++ b/test/addons/06_factory_of_wrapped_objects/myobject.h
@@ -0,0 +1,28 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// myobject.h
+#ifndef MYOBJECT_H  // NOLINT(build/header_guard)
+#define MYOBJECT_H  // NOLINT(build/header_guard)
+
+#include <node.h>
+#include <node_object_wrap.h>
+
+namespace demo {
+
+class MyObject : public node::ObjectWrap {
+ public:
+  static void Init(v8::Isolate* isolate);
+  static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ private:
+  explicit MyObject(double value = 0);
+  ~MyObject();
+
+  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
+  static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
+  static v8::Persistent<v8::Function> constructor;
+  double value_;
+};
+
+}  // namespace demo
+
+#endif  // NOLINT(build/header_guard)
diff --git a/test/addons/06_factory_of_wrapped_objects/test.js b/test/addons/06_factory_of_wrapped_objects/test.js
new file mode 100644
index 00000000000000..1d02c0e1a20bff
--- /dev/null
+++ b/test/addons/06_factory_of_wrapped_objects/test.js
@@ -0,0 +1,21 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+'use strict';
+const common = require('../../common');
+// test.js
+const createObject = require(`./build/${common.buildType}/binding`);
+
+const obj = createObject(10);
+console.log(obj.plusOne());
+// Prints: 11
+console.log(obj.plusOne());
+// Prints: 12
+console.log(obj.plusOne());
+// Prints: 13
+
+const obj2 = createObject(20);
+console.log(obj2.plusOne());
+// Prints: 21
+console.log(obj2.plusOne());
+// Prints: 22
+console.log(obj2.plusOne());
+// Prints: 23
diff --git a/test/addons/07_passing_wrapped_objects_around/binding.cc b/test/addons/07_passing_wrapped_objects_around/binding.cc
new file mode 100644
index 00000000000000..36ba3a710ec18b
--- /dev/null
+++ b/test/addons/07_passing_wrapped_objects_around/binding.cc
@@ -0,0 +1,42 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// binding.cc
+#include <node.h>
+#include <node_object_wrap.h>
+#include "myobject.h"
+
+namespace demo {
+
+using v8::FunctionCallbackInfo;
+using v8::Isolate;
+using v8::Local;
+using v8::Number;
+using v8::Object;
+using v8::String;
+using v8::Value;
+
+void CreateObject(const FunctionCallbackInfo<Value>& args) {
+  MyObject::NewInstance(args);
+}
+
+void Add(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = args.GetIsolate();
+
+  MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>(
+      args[0]->ToObject());
+  MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>(
+      args[1]->ToObject());
+
+  double sum = obj1->value() + obj2->value();
+  args.GetReturnValue().Set(Number::New(isolate, sum));
+}
+
+void InitAll(Local<Object> exports) {
+  MyObject::Init(exports->GetIsolate());
+
+  NODE_SET_METHOD(exports, "createObject", CreateObject);
+  NODE_SET_METHOD(exports, "add", Add);
+}
+
+NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)
+
+}  // namespace demo
diff --git a/test/addons/07_passing_wrapped_objects_around/binding.gyp b/test/addons/07_passing_wrapped_objects_around/binding.gyp
new file mode 100644
index 00000000000000..5d967d0948c39b
--- /dev/null
+++ b/test/addons/07_passing_wrapped_objects_around/binding.gyp
@@ -0,0 +1,2 @@
+# Auto-generated by `node tools/doc/addon-verify.js`
+{"targets":[{"target_name":"binding","defines":["V8_DEPRECATION_WARNINGS=1"],"sources":["binding.cc","myobject.h","myobject.cc","test.js"]}]}
\ No newline at end of file
diff --git a/test/addons/07_passing_wrapped_objects_around/myobject.cc b/test/addons/07_passing_wrapped_objects_around/myobject.cc
new file mode 100644
index 00000000000000..3dd5bb69760275
--- /dev/null
+++ b/test/addons/07_passing_wrapped_objects_around/myobject.cc
@@ -0,0 +1,70 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// myobject.cc
+#include <node.h>
+#include "myobject.h"
+
+namespace demo {
+
+using v8::Context;
+using v8::Function;
+using v8::FunctionCallbackInfo;
+using v8::FunctionTemplate;
+using v8::Isolate;
+using v8::Local;
+using v8::Object;
+using v8::Persistent;
+using v8::String;
+using v8::Value;
+
+Persistent<Function> MyObject::constructor;
+
+MyObject::MyObject(double value) : value_(value) {
+}
+
+MyObject::~MyObject() {
+}
+
+void MyObject::Init(Isolate* isolate) {
+  // Prepare constructor template
+  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
+  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
+  tpl->InstanceTemplate()->SetInternalFieldCount(1);
+
+  constructor.Reset(isolate, tpl->GetFunction());
+}
+
+void MyObject::New(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = args.GetIsolate();
+
+  if (args.IsConstructCall()) {
+    // Invoked as constructor: `new MyObject(...)`
+    double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
+    MyObject* obj = new MyObject(value);
+    obj->Wrap(args.This());
+    args.GetReturnValue().Set(args.This());
+  } else {
+    // Invoked as plain function `MyObject(...)`, turn into construct call.
+    const int argc = 1;
+    Local<Value> argv[argc] = { args[0] };
+    Local<Context> context = isolate->GetCurrentContext();
+    Local<Function> cons = Local<Function>::New(isolate, constructor);
+    Local<Object> instance =
+        cons->NewInstance(context, argc, argv).ToLocalChecked();
+    args.GetReturnValue().Set(instance);
+  }
+}
+
+void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = args.GetIsolate();
+
+  const unsigned argc = 1;
+  Local<Value> argv[argc] = { args[0] };
+  Local<Function> cons = Local<Function>::New(isolate, constructor);
+  Local<Context> context = isolate->GetCurrentContext();
+  Local<Object> instance =
+      cons->NewInstance(context, argc, argv).ToLocalChecked();
+
+  args.GetReturnValue().Set(instance);
+}
+
+}  // namespace demo
diff --git a/test/addons/07_passing_wrapped_objects_around/myobject.h b/test/addons/07_passing_wrapped_objects_around/myobject.h
new file mode 100644
index 00000000000000..5d430a20813460
--- /dev/null
+++ b/test/addons/07_passing_wrapped_objects_around/myobject.h
@@ -0,0 +1,28 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// myobject.h
+#ifndef MYOBJECT_H  // NOLINT(build/header_guard)
+#define MYOBJECT_H  // NOLINT(build/header_guard)
+
+#include <node.h>
+#include <node_object_wrap.h>
+
+namespace demo {
+
+class MyObject : public node::ObjectWrap {
+ public:
+  static void Init(v8::Isolate* isolate);
+  static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
+  inline double value() const { return value_; }
+
+ private:
+  explicit MyObject(double value = 0);
+  ~MyObject();
+
+  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
+  static v8::Persistent<v8::Function> constructor;
+  double value_;
+};
+
+}  // namespace demo
+
+#endif  // NOLINT(build/header_guard)
diff --git a/test/addons/07_passing_wrapped_objects_around/test.js b/test/addons/07_passing_wrapped_objects_around/test.js
new file mode 100644
index 00000000000000..0175835c7c3b9c
--- /dev/null
+++ b/test/addons/07_passing_wrapped_objects_around/test.js
@@ -0,0 +1,12 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+'use strict';
+const common = require('../../common');
+// test.js
+const binding = require(`./build/${common.buildType}/binding`);
+
+const obj1 = binding.createObject(10);
+const obj2 = binding.createObject(20);
+const result = binding.add(obj1, obj2);
+
+console.log(result);
+// Prints: 30
diff --git a/test/addons/08_void_atexitcallback_args/binding.cc b/test/addons/08_void_atexitcallback_args/binding.cc
new file mode 100644
index 00000000000000..33fdc6baf95f08
--- /dev/null
+++ b/test/addons/08_void_atexitcallback_args/binding.cc
@@ -0,0 +1,47 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+// binding.cc
+#include <assert.h>
+#include <stdlib.h>
+#include <node.h>
+
+namespace demo {
+
+using node::AtExit;
+using v8::HandleScope;
+using v8::Isolate;
+using v8::Local;
+using v8::Object;
+
+static char cookie[] = "yum yum";
+static int at_exit_cb1_called = 0;
+static int at_exit_cb2_called = 0;
+
+static void at_exit_cb1(void* arg) {
+  Isolate* isolate = static_cast<Isolate*>(arg);
+  HandleScope scope(isolate);
+  Local<Object> obj = Object::New(isolate);
+  assert(!obj.IsEmpty());  // assert VM is still alive
+  assert(obj->IsObject());
+  at_exit_cb1_called++;
+}
+
+static void at_exit_cb2(void* arg) {
+  assert(arg == static_cast<void*>(cookie));
+  at_exit_cb2_called++;
+}
+
+static void sanity_check(void*) {
+  assert(at_exit_cb1_called == 1);
+  assert(at_exit_cb2_called == 2);
+}
+
+void init(Local<Object> exports) {
+  AtExit(at_exit_cb2, cookie);
+  AtExit(at_exit_cb2, cookie);
+  AtExit(at_exit_cb1, exports->GetIsolate());
+  AtExit(sanity_check);
+}
+
+NODE_MODULE(NODE_GYP_MODULE_NAME, init)
+
+}  // namespace demo
diff --git a/test/addons/08_void_atexitcallback_args/binding.gyp b/test/addons/08_void_atexitcallback_args/binding.gyp
new file mode 100644
index 00000000000000..6cd05e5bba3ccd
--- /dev/null
+++ b/test/addons/08_void_atexitcallback_args/binding.gyp
@@ -0,0 +1,2 @@
+# Auto-generated by `node tools/doc/addon-verify.js`
+{"targets":[{"target_name":"binding","defines":["V8_DEPRECATION_WARNINGS=1"],"sources":["binding.cc","test.js"]}]}
\ No newline at end of file
diff --git a/test/addons/08_void_atexitcallback_args/test.js b/test/addons/08_void_atexitcallback_args/test.js
new file mode 100644
index 00000000000000..43672ff8233117
--- /dev/null
+++ b/test/addons/08_void_atexitcallback_args/test.js
@@ -0,0 +1,5 @@
+// Auto-generated by `node tools/doc/addon-verify.js`
+'use strict';
+const common = require('../../common');
+// test.js
+require(`./build/${common.buildType}/binding`);
diff --git a/test/addons/openssl-client-cert-engine/binding.cc b/test/addons/openssl-client-cert-engine/binding.cc
new file mode 100644
index 00000000000000..60340815ed2038
--- /dev/null
+++ b/test/addons/openssl-client-cert-engine/binding.cc
@@ -0,0 +1,6 @@
+#include "node.h"
+#include "v8.h"
+
+inline void Initialize(v8::Local<v8::Object>) {}
+
+NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
diff --git a/test/addons/openssl-client-cert-engine/binding.gyp b/test/addons/openssl-client-cert-engine/binding.gyp
index b069e43429c12b..48a6aa3480d44c 100644
--- a/test/addons/openssl-client-cert-engine/binding.gyp
+++ b/test/addons/openssl-client-cert-engine/binding.gyp
@@ -1,5 +1,9 @@
 {
   'targets': [
+    {
+      'target_name': 'binding',
+      'sources': [ 'binding.cc' ],
+    },
     {
       'target_name': 'testengine',
       'type': 'none',
diff --git a/tools/doc/addon-verify.js b/tools/doc/addon-verify.js
index 4da99d64d72da1..51ba75ca45ac68 100644
--- a/tools/doc/addon-verify.js
+++ b/tools/doc/addon-verify.js
@@ -5,6 +5,7 @@ const fs = require('fs');
 const path = require('path');
 const marked = require('marked');
 
+const auto = 'Auto-generated by `node tools/doc/addon-verify.js`';
 const rootDir = path.resolve(__dirname, '..', '..');
 const doc = path.resolve(rootDir, 'doc', 'api', 'addons.md');
 const verifyDir = path.resolve(rootDir, 'test', 'addons');
@@ -49,12 +50,20 @@ for (const header in addons) {
 
   files = Object.entries(files).map(([name, content]) => {
     if (name === 'test.js') content = boilerplate(name, content);
+    content = `// ${auto}\n${content}`;
+    if (name.endsWith('.h')) {
+      content = content.replace(/(#(ifndef|define) \w+_H)/g,
+                                '$1  // NOLINT(build/header_guard)');
+      content = content.replace(/(#endif)$/,
+                                '$1  // NOLINT(build/header_guard)');
+    }
+    if (!content.endsWith('\n')) content += '\n';  // Pacify linter.
     return { name, content, path: path.resolve(dir, name) };
   });
 
   files.push({
     path: path.resolve(dir, 'binding.gyp'),
-    content: JSON.stringify({
+    content: `# ${auto}\n` + JSON.stringify({
       targets: [
         {
           target_name: 'binding',

From f1a26f695be426326058f9f62505ea7490c6f959 Mon Sep 17 00:00:00 2001
From: Ben Noordhuis <info@bnoordhuis.nl>
Date: Sat, 20 Jan 2018 01:00:34 +0100
Subject: [PATCH 4/6] build,tools: check freshness of doc addons

Add a `--check` flag to `tools/doc/addon-verify.js` and use that in the
`make test` target to determine if the generated files in `test/addons`
are fresh or stale.
---
 Makefile                  |  6 +++++-
 tools/doc/addon-verify.js | 31 ++++++++++++++++++++++---------
 2 files changed, 27 insertions(+), 10 deletions(-)

diff --git a/Makefile b/Makefile
index f988bb4e3681f6..6efb50950e1501 100644
--- a/Makefile
+++ b/Makefile
@@ -225,7 +225,7 @@ v8:
 
 .PHONY: test
 # This does not run tests of third-party libraries inside deps.
-test: all build-addons	## Runs default tests, linters, and builds docs.
+test: all check-doc-addons build-addons	## Runs default tests, linters, and builds docs.
 	$(MAKE) -s doc-only
 	$(MAKE) -s lint
 	$(MAKE) -s cctest
@@ -234,6 +234,10 @@ test: all build-addons	## Runs default tests, linters, and builds docs.
 		$(CI_NATIVE_SUITES) \
 		$(CI_DOC)
 
+.PHONY: check-doc-addons
+check-doc-addons: $(NODE)
+	$(NODE) tools/doc/addon-verify.js --check
+
 .PHONY: test-only
 test-only: all build-addons	## For a quick test, does not run linter or build docs.
 	$(MAKE) cctest
diff --git a/tools/doc/addon-verify.js b/tools/doc/addon-verify.js
index 51ba75ca45ac68..e9b43a2b60cfff 100644
--- a/tools/doc/addon-verify.js
+++ b/tools/doc/addon-verify.js
@@ -10,6 +10,9 @@ const rootDir = path.resolve(__dirname, '..', '..');
 const doc = path.resolve(rootDir, 'doc', 'api', 'addons.md');
 const verifyDir = path.resolve(rootDir, 'test', 'addons');
 
+const changed = [];
+const checkOnly = process.argv.includes('--check');
+
 let id = 0;
 let currentHeader;
 
@@ -76,12 +79,6 @@ for (const header in addons) {
     })
   });
 
-  try {
-    fs.mkdirSync(dir);
-  } catch (e) {
-    strictEqual(e.code, 'EEXIST');
-  }
-
   for (const file of files) {
     let content;
     try {
@@ -91,13 +88,29 @@ for (const header in addons) {
     }
 
     // Only update when file content has changed to prevent unneeded rebuilds.
-    if (content !== file.content) {
-      fs.writeFileSync(file.path, file.content);
-      console.log('wrote', file.path);
+    if (content === file.content) continue;
+    changed.push(file);
+
+    if (checkOnly) continue;
+
+    try {
+      fs.mkdirSync(dir);
+    } catch (e) {
+      strictEqual(e.code, 'EEXIST');
     }
+
+    fs.writeFileSync(file.path, file.content);
+    console.log('wrote', file.path);
   }
 }
 
+if (checkOnly && changed.length > 0) {
+  console.error('The following files are out of date:');
+  for (const { path } of changed) console.error('  ', path);
+  console.error('Run `node tools/doc/addon-verify.js` to update.');
+  process.exit(1);
+}
+
 function boilerplate(name, content) {
   return `'use strict';
 const common = require('../../common');

From 8080e23ecaf3998e99b828334bae29fc9c9a4e82 Mon Sep 17 00:00:00 2001
From: Ben Noordhuis <info@bnoordhuis.nl>
Date: Sat, 20 Jan 2018 01:00:34 +0100
Subject: [PATCH 5/6] test: fix flaky cluster unix socket test

Ensure `common.tmpDir` exists before trying to chdir into it.  Fixes a
"ENOENT: no such file or directory, uv_chdir" error when the temporary
directory is removed before running the test.
---
 test/parallel/test-cluster-net-listen-relative-path.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/test/parallel/test-cluster-net-listen-relative-path.js b/test/parallel/test-cluster-net-listen-relative-path.js
index 0fcc6a0dca4160..2f95d05203d424 100644
--- a/test/parallel/test-cluster-net-listen-relative-path.js
+++ b/test/parallel/test-cluster-net-listen-relative-path.js
@@ -20,6 +20,7 @@ assert.strictEqual(path.resolve(socketDir, socketName).length > 100, true,
 
 if (cluster.isMaster) {
   // ensure that the worker exits peacefully
+  common.refreshTmpDir();
   process.chdir(common.tmpDir);
   fs.mkdirSync(socketDir);
   cluster.fork().on('exit', common.mustCall(function(statusCode) {

From 89b814eb6f536e93a5d6b5eb1f663722f0de0bba Mon Sep 17 00:00:00 2001
From: Ben Noordhuis <info@bnoordhuis.nl>
Date: Sat, 20 Jan 2018 14:19:43 +0100
Subject: [PATCH 6/6] fixup! smartos-only libnode-induced breakage...

---
 node.gyp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/node.gyp b/node.gyp
index 4d47bc37191a22..54bdb87669f176 100644
--- a/node.gyp
+++ b/node.gyp
@@ -542,8 +542,8 @@
             }],
             [ 'OS!="mac" and OS!="linux"', {
               'sources': [
-                '<(OBJ_DIR)/node/src/node_dtrace_provider.o',
-                '<(OBJ_DIR)/node/src/node_dtrace_ustack.o',
+                '<(OBJ_DIR)/<(node_lib_target_name)/src/node_dtrace_provider.o',
+                '<(OBJ_DIR)/<(node_lib_target_name)/src/node_dtrace_ustack.o',
               ]
             }
           ] ]