diff --git a/lib/manager/composer/artifacts.ts b/lib/manager/composer/artifacts.ts
index 0780314bf8217cc04ed3f4dd3235de67b5bcc374..cf209a4d60202c3c5e566ffeb6bebd9ac2dd8dae 100644
--- a/lib/manager/composer/artifacts.ts
+++ b/lib/manager/composer/artifacts.ts
@@ -118,7 +118,7 @@ export async function updateArtifacts(
       cmd = 'composer';
     } else {
       logger.warn({ config }, 'Unsupported binarySource');
-      cmd = 'bundle';
+      cmd = 'composer';
     }
     let args;
     if (config.isLockFileMaintenance) {
diff --git a/lib/manager/gomod/artifacts.ts b/lib/manager/gomod/artifacts.ts
index f802b73960270ae0b7c00bbc3d01bff959d13854..b182ad8fa46d72370b7a5e7b8e65790dcf7d11cb 100644
--- a/lib/manager/gomod/artifacts.ts
+++ b/lib/manager/gomod/artifacts.ts
@@ -77,7 +77,7 @@ export async function updateArtifacts(
       cmd = 'go';
     } else {
       logger.warn({ config }, 'Unsupported binarySource');
-      cmd = 'bundle';
+      cmd = 'go';
     }
     let args = 'get -d ./...';
     if (cmd.includes('.insteadOf')) {
diff --git a/lib/manager/pip_setup/extract.ts b/lib/manager/pip_setup/extract.ts
index 8e41dd5070de6e9f65c78d4bae19d10ba62cd34a..a9954ee84e47215c44bbb1de021f46ab28aa276b 100644
--- a/lib/manager/pip_setup/extract.ts
+++ b/lib/manager/pip_setup/extract.ts
@@ -49,7 +49,6 @@ export async function extractSetupFile(
   const cwd = config.localDir;
   let cmd: string;
   const args = [`"${join(__dirname, 'extract.py')}"`, `"${packageFile}"`];
-  // istanbul ignore if
   if (config.binarySource === 'docker') {
     logger.info('Running python via docker');
     await exec(`docker pull renovate/pip`);
diff --git a/test/execUtil.ts b/test/execUtil.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5014cbd52d93f0512e5b84540bba66e4f44992c7
--- /dev/null
+++ b/test/execUtil.ts
@@ -0,0 +1,66 @@
+import { exec as _exec } from 'child_process';
+import { ExecOptions } from '../lib/util/exec';
+
+type CallOptions = ExecOptions | null | undefined;
+
+export type ExecResult = { stdout: string; stderr: string } | Error;
+
+interface ExecSnapshot {
+  cmd: string;
+  options?: ExecOptions | null | undefined;
+}
+
+export type ExecSnapshots = ExecSnapshot[];
+
+export function execSnapshot(cmd: string, options?: CallOptions): ExecSnapshot {
+  const snapshot = {
+    cmd,
+    options,
+  };
+
+  const str = JSON.stringify(snapshot, (k, v) => (v === undefined ? null : v));
+
+  const cwd = process.cwd().replace(/\\(\w)/g, '/$1');
+  return JSON.parse(
+    str
+      .replace(/\\(\w)/g, '/$1')
+      .split(cwd)
+      .join('/root/project')
+  );
+}
+
+const defaultExecResult = { stdout: '', stderr: '' };
+
+export function mockExecAll(
+  execFn: jest.Mock<typeof _exec>,
+  execResult: ExecResult = defaultExecResult
+): ExecSnapshots {
+  const snapshots = [];
+  execFn.mockImplementation((cmd, options, callback) => {
+    snapshots.push(execSnapshot(cmd, options));
+    if (execResult instanceof Error) {
+      throw execResult;
+    }
+    callback(null, execResult);
+    return undefined;
+  });
+  return snapshots;
+}
+
+export function mockExecSequence(
+  execFn: jest.Mock<typeof _exec>,
+  execResults: ExecResult[]
+): ExecSnapshots {
+  const snapshots = [];
+  execResults.forEach(execResult => {
+    execFn.mockImplementationOnce((cmd, options, callback) => {
+      snapshots.push(execSnapshot(cmd, options));
+      if (execResult instanceof Error) {
+        throw execResult;
+      }
+      callback(null, execResult);
+      return undefined;
+    });
+  });
+  return snapshots;
+}
diff --git a/test/manager/bundler/__snapshots__/artifacts.spec.ts.snap b/test/manager/bundler/__snapshots__/artifacts.spec.ts.snap
index 7f92845d826d3a3ab4296ea5706e67feddb20ae6..5b255c6b07ccec515b5881f4cec8fa2b509a6e01 100644
--- a/test/manager/bundler/__snapshots__/artifacts.spec.ts.snap
+++ b/test/manager/bundler/__snapshots__/artifacts.spec.ts.snap
@@ -11,7 +11,24 @@ Array [
 ]
 `;
 
-exports[`bundler.updateArtifacts() Docker .ruby-version 2`] = `"docker run --rm -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/ruby:1.2.0 bash -l -c \\"ruby --version && gem install bundler --no-document && bundle lock --update \\""`;
+exports[`bundler.updateArtifacts() Docker .ruby-version 2`] = `
+Array [
+  Object {
+    "cmd": "docker run --rm -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/ruby:1.2.0 bash -l -c \\"ruby --version && gem install bundler --no-document && bundle lock --update \\"",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
 
 exports[`bundler.updateArtifacts() Docker compatibility options 1`] = `
 Array [
@@ -24,10 +41,46 @@ Array [
 ]
 `;
 
-exports[`bundler.updateArtifacts() Docker compatibility options 2`] = `"docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/ruby:latest bash -l -c \\"ruby --version && gem install bundler -v 3.2.1 --no-document && bundle lock --update \\""`;
+exports[`bundler.updateArtifacts() Docker compatibility options 2`] = `
+Array [
+  Object {
+    "cmd": "docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/ruby:latest bash -l -c \\"ruby --version && gem install bundler -v 3.2.1 --no-document && bundle lock --update \\"",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
 
 exports[`bundler.updateArtifacts() returns null if Gemfile.lock was not changed 1`] = `null`;
 
+exports[`bundler.updateArtifacts() returns null if Gemfile.lock was not changed 2`] = `
+Array [
+  Object {
+    "cmd": "bundle lock --update ",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
 exports[`bundler.updateArtifacts() works explicit global binarySource 1`] = `
 Array [
   Object {
@@ -39,6 +92,25 @@ Array [
 ]
 `;
 
+exports[`bundler.updateArtifacts() works explicit global binarySource 2`] = `
+Array [
+  Object {
+    "cmd": "bundle lock --update ",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
 exports[`bundler.updateArtifacts() works for default binarySource 1`] = `
 Array [
   Object {
@@ -49,3 +121,22 @@ Array [
   },
 ]
 `;
+
+exports[`bundler.updateArtifacts() works for default binarySource 2`] = `
+Array [
+  Object {
+    "cmd": "bundle lock --update ",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
diff --git a/test/manager/bundler/artifacts.spec.ts b/test/manager/bundler/artifacts.spec.ts
index 7d91f5d3628fbf91551732f9abe0910cebe51573..a325af14760e57999e9066e02668432cc77ec96e 100644
--- a/test/manager/bundler/artifacts.spec.ts
+++ b/test/manager/bundler/artifacts.spec.ts
@@ -5,6 +5,7 @@ import { updateArtifacts } from '../../../lib/manager/bundler';
 import { platform as _platform } from '../../../lib/platform';
 import * as _datasource from '../../../lib/datasource/docker';
 import { mocked } from '../../util';
+import { mockExecAll } from '../../execUtil';
 
 const fs: jest.Mocked<typeof _fs> = _fs as any;
 const exec: jest.Mock<typeof _exec> = _exec as any;
@@ -17,13 +18,28 @@ jest.mock('../../../lib/platform');
 jest.mock('../../../lib/datasource/docker');
 
 let config;
+let processEnv;
 
 describe('bundler.updateArtifacts()', () => {
   beforeEach(() => {
     jest.resetAllMocks();
+    jest.resetModules();
+
     config = {
       localDir: '/tmp/github/some/repo',
     };
+
+    processEnv = process.env;
+    process.env = {
+      HTTP_PROXY: 'http://example.com',
+      HTTPS_PROXY: 'https://example.com',
+      NO_PROXY: 'localhost',
+      HOME: '/home/user',
+      PATH: '/tmp/path',
+    };
+  });
+  afterEach(() => {
+    process.env = processEnv;
   });
   it('returns null by default', async () => {
     expect(await updateArtifacts('', [], '', config)).toBeNull();
@@ -31,10 +47,7 @@ describe('bundler.updateArtifacts()', () => {
   it('returns null if Gemfile.lock was not changed', async () => {
     platform.getFile.mockResolvedValueOnce('Current Gemfile.lock');
     fs.outputFile.mockResolvedValueOnce(null as never);
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     platform.getRepoStatus.mockResolvedValueOnce({
       modified: [],
     } as Git.StatusResult);
@@ -42,14 +55,12 @@ describe('bundler.updateArtifacts()', () => {
     expect(
       await updateArtifacts('Gemfile', [], 'Updated Gemfile content', config)
     ).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('works for default binarySource', async () => {
     platform.getFile.mockResolvedValueOnce('Current Gemfile.lock');
     fs.outputFile.mockResolvedValueOnce(null as never);
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     platform.getRepoStatus.mockResolvedValueOnce({
       modified: ['Gemfile.lock'],
     } as Git.StatusResult);
@@ -57,14 +68,12 @@ describe('bundler.updateArtifacts()', () => {
     expect(
       await updateArtifacts('Gemfile', [], 'Updated Gemfile content', config)
     ).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('works explicit global binarySource', async () => {
     platform.getFile.mockResolvedValueOnce('Current Gemfile.lock');
     fs.outputFile.mockResolvedValueOnce(null as never);
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     platform.getRepoStatus.mockResolvedValueOnce({
       modified: ['Gemfile.lock'],
     } as Git.StatusResult);
@@ -75,10 +84,10 @@ describe('bundler.updateArtifacts()', () => {
         binarySource: 'global',
       })
     ).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
   });
   describe('Docker', () => {
     it('.ruby-version', async () => {
-      let dockerCommand = null;
       platform.getFile.mockResolvedValueOnce('Current Gemfile.lock');
       fs.outputFile.mockResolvedValueOnce(null as never);
       platform.getFile.mockResolvedValueOnce('1.2.0');
@@ -89,11 +98,7 @@ describe('bundler.updateArtifacts()', () => {
           { version: '1.3.0' },
         ],
       });
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        dockerCommand = cmd;
-        callback(null, { stdout: '', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec);
       platform.getRepoStatus.mockResolvedValueOnce({
         modified: ['Gemfile.lock'],
       } as Git.StatusResult);
@@ -104,10 +109,9 @@ describe('bundler.updateArtifacts()', () => {
           binarySource: 'docker',
         })
       ).toMatchSnapshot();
-      expect(dockerCommand.replace(/\\(\w)/g, '/$1')).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
     it('compatibility options', async () => {
-      let dockerCommand = null;
       platform.getFile.mockResolvedValueOnce('Current Gemfile.lock');
       fs.outputFile.mockResolvedValueOnce(null as never);
       datasource.getPkgReleases.mockResolvedValueOnce({
@@ -117,11 +121,7 @@ describe('bundler.updateArtifacts()', () => {
           { version: '1.3.0' },
         ],
       });
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        dockerCommand = cmd;
-        callback(null, { stdout: '', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec);
       platform.getRepoStatus.mockResolvedValueOnce({
         modified: ['Gemfile.lock'],
       } as Git.StatusResult);
@@ -137,7 +137,7 @@ describe('bundler.updateArtifacts()', () => {
           },
         })
       ).toMatchSnapshot();
-      expect(dockerCommand.replace(/\\(\w)/g, '/$1')).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
   });
 });
diff --git a/test/manager/cargo/__snapshots__/artifacts.spec.ts.snap b/test/manager/cargo/__snapshots__/artifacts.spec.ts.snap
index 6e4744b51073b3b22dfe667f1727c44813de82a1..cb423da45162027123492013a067d3746a0b2c1f 100644
--- a/test/manager/cargo/__snapshots__/artifacts.spec.ts.snap
+++ b/test/manager/cargo/__snapshots__/artifacts.spec.ts.snap
@@ -11,4 +11,59 @@ Array [
 ]
 `;
 
-exports[`.updateArtifacts() returns updated Cargo.lock with docker 1`] = `"docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/rust cargo update --manifest-path /tmp/github/some/repo/Cargo.toml --package dep1"`;
+exports[`.updateArtifacts() returns null if unchanged 1`] = `
+Array [
+  Object {
+    "cmd": "cargo update --manifest-path /tmp/github/some/repo/Cargo.toml --package dep1",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns updated Cargo.lock 1`] = `
+Array [
+  Object {
+    "cmd": "cargo update --manifest-path /tmp/github/some/repo/Cargo.toml --package dep1",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns updated Cargo.lock with docker 1`] = `
+Array [
+  Object {
+    "cmd": "docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/rust cargo update --manifest-path /tmp/github/some/repo/Cargo.toml --package dep1",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
diff --git a/test/manager/cargo/artifacts.spec.ts b/test/manager/cargo/artifacts.spec.ts
index b667dbe3c793025d457fecbd2d383d2281f6a619..f7a3e5071e96f13208841f394f84f2cbfbf20c76 100644
--- a/test/manager/cargo/artifacts.spec.ts
+++ b/test/manager/cargo/artifacts.spec.ts
@@ -3,6 +3,7 @@ import { exec as _exec } from 'child_process';
 import * as cargo from '../../../lib/manager/cargo/artifacts';
 import { platform as _platform } from '../../../lib/platform';
 import { mocked } from '../../util';
+import { mockExecAll } from '../../execUtil';
 
 jest.mock('fs-extra');
 jest.mock('child_process');
@@ -15,12 +16,24 @@ const config = {
   localDir: '/tmp/github/some/repo',
 };
 
+let processEnv;
+
 describe('.updateArtifacts()', () => {
   beforeEach(() => {
     jest.resetAllMocks();
+    jest.resetModules();
+
+    processEnv = process.env;
+    process.env = {
+      HTTP_PROXY: 'http://example.com',
+      HTTPS_PROXY: 'https://example.com',
+      NO_PROXY: 'localhost',
+      HOME: '/home/user',
+      PATH: '/tmp/path',
+    };
   });
   afterEach(() => {
-    delete global.trustLevel;
+    process.env = processEnv;
   });
   it('returns null if no Cargo.lock found', async () => {
     const updatedDeps = ['dep1'];
@@ -35,40 +48,29 @@ describe('.updateArtifacts()', () => {
   });
   it('returns null if unchanged', async () => {
     platform.getFile.mockResolvedValueOnce('Current Cargo.lock');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('Current Cargo.lock' as any);
     const updatedDeps = ['dep1'];
     expect(
       await cargo.updateArtifacts('Cargo.toml', updatedDeps, '', config)
     ).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('returns updated Cargo.lock', async () => {
     platform.getFile.mockResolvedValueOnce('Old Cargo.lock');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('New Cargo.lock' as any);
     const updatedDeps = ['dep1'];
-    global.trustLevel = 'high';
     expect(
       await cargo.updateArtifacts('Cargo.toml', updatedDeps, '{}', config)
     ).not.toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('returns updated Cargo.lock with docker', async () => {
-    let dockerCommand = null;
     platform.getFile.mockResolvedValueOnce('Old Cargo.lock');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      dockerCommand = cmd;
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('New Cargo.lock' as any);
     const updatedDeps = ['dep1'];
-    global.trustLevel = 'high';
     expect(
       await cargo.updateArtifacts('Cargo.toml', updatedDeps, '{}', {
         ...config,
@@ -76,7 +78,7 @@ describe('.updateArtifacts()', () => {
         dockerUser: 'foobar',
       })
     ).not.toBeNull();
-    expect(dockerCommand.replace(/\\(\w)/g, '/$1')).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('catches errors', async () => {
     platform.getFile.mockResolvedValueOnce('Current Cargo.lock');
diff --git a/test/manager/composer/__snapshots__/artifacts.spec.ts.snap b/test/manager/composer/__snapshots__/artifacts.spec.ts.snap
index 11db3911996def58f5e37e68106d847a788e2054..d3abf88eff6176c6a573fcfbe823bba20208dfd2 100644
--- a/test/manager/composer/__snapshots__/artifacts.spec.ts.snap
+++ b/test/manager/composer/__snapshots__/artifacts.spec.ts.snap
@@ -22,4 +22,122 @@ Array [
 ]
 `;
 
-exports[`.updateArtifacts() supports docker mode 1`] = `"docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache/others/composer\\":\\"/tmp/renovate/cache/others/composer\\" -e COMPOSER_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/composer composer update --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader"`;
+exports[`.updateArtifacts() performs lockFileMaintenance 1`] = `
+Array [
+  Object {
+    "cmd": "composer install --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "COMPOSER_CACHE_DIR": "/tmp/renovate/cache/others/composer",
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns null if unchanged 1`] = `
+Array [
+  Object {
+    "cmd": "composer update --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "COMPOSER_CACHE_DIR": "/tmp/renovate/cache/others/composer",
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns updated composer.lock 1`] = `
+Array [
+  Object {
+    "cmd": "composer update --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "COMPOSER_CACHE_DIR": "/tmp/renovate/cache/others/composer",
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() supports docker mode 1`] = `
+Array [
+  Object {
+    "cmd": "docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache/others/composer\\":\\"/tmp/renovate/cache/others/composer\\" -e COMPOSER_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/composer composer update --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "COMPOSER_CACHE_DIR": "/tmp/renovate/cache/others/composer",
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() supports global mode 1`] = `
+Array [
+  Object {
+    "cmd": "composer update --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "COMPOSER_CACHE_DIR": "/tmp/renovate/cache/others/composer",
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() uses hostRules to write auth.json 1`] = `
+Array [
+  Object {
+    "cmd": "composer update --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "COMPOSER_CACHE_DIR": "/tmp/renovate/cache/others/composer",
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
diff --git a/test/manager/composer/artifacts.spec.ts b/test/manager/composer/artifacts.spec.ts
index 154bdb75f5c9e90c455dab43ca62773157382228..1207685148ef68dc718a30bc25ead9f47f3120a9 100644
--- a/test/manager/composer/artifacts.spec.ts
+++ b/test/manager/composer/artifacts.spec.ts
@@ -4,6 +4,7 @@ import * as composer from '../../../lib/manager/composer/artifacts';
 import { platform as _platform } from '../../../lib/platform';
 import { mocked } from '../../util';
 import { StatusResult } from '../../../lib/platform/git/storage';
+import { mockExecAll } from '../../execUtil';
 
 jest.mock('fs-extra');
 jest.mock('child_process');
@@ -20,12 +21,24 @@ const config = {
   cacheDir: '/tmp/renovate/cache',
 };
 
+let processEnv;
+
 describe('.updateArtifacts()', () => {
   beforeEach(() => {
     jest.resetAllMocks();
+    jest.resetModules();
+
+    processEnv = process.env;
+    process.env = {
+      HTTP_PROXY: 'http://example.com',
+      HTTPS_PROXY: 'https://example.com',
+      NO_PROXY: 'localhost',
+      HOME: '/home/user',
+      PATH: '/tmp/path',
+    };
   });
   afterEach(() => {
-    delete global.trustLevel;
+    process.env = processEnv;
   });
   it('returns if no composer.lock found', async () => {
     expect(
@@ -34,25 +47,20 @@ describe('.updateArtifacts()', () => {
   });
   it('returns null if unchanged', async () => {
     platform.getFile.mockResolvedValueOnce('Current composer.lock');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('Current composer.lock' as any);
     platform.getRepoStatus.mockResolvedValue({ modified: [] } as StatusResult);
     expect(
       await composer.updateArtifacts('composer.json', [], '{}', config)
     ).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('uses hostRules to write auth.json', async () => {
     platform.getFile.mockResolvedValueOnce('Current composer.lock');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('Current composer.lock' as any);
     const authConfig = {
-      localDir: '/tmp/github/some/repo',
+      ...config,
       registryUrls: ['https://packagist.renovatebot.com'],
     };
     hostRules.find.mockReturnValue({
@@ -63,28 +71,23 @@ describe('.updateArtifacts()', () => {
     expect(
       await composer.updateArtifacts('composer.json', [], '{}', authConfig)
     ).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('returns updated composer.lock', async () => {
     platform.getFile.mockResolvedValueOnce('Current composer.lock');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('New composer.lock' as any);
-    global.trustLevel = 'high';
     platform.getRepoStatus.mockResolvedValue({
       modified: ['composer.lock'],
     } as StatusResult);
     expect(
       await composer.updateArtifacts('composer.json', [], '{}', config)
     ).not.toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('performs lockFileMaintenance', async () => {
     platform.getFile.mockResolvedValueOnce('Current composer.lock');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('New composer.lock' as any);
     platform.getRepoStatus.mockResolvedValue({
       modified: ['composer.lock'],
@@ -95,16 +98,12 @@ describe('.updateArtifacts()', () => {
         isLockFileMaintenance: true,
       })
     ).not.toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('supports docker mode', async () => {
     platform.getFile.mockResolvedValueOnce('Current composer.lock');
 
-    let dockerCommand = null;
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      dockerCommand = cmd;
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
 
     fs.readFile.mockReturnValueOnce('New composer.lock' as any);
     expect(
@@ -114,14 +113,11 @@ describe('.updateArtifacts()', () => {
         dockerUser: 'foobar',
       })
     ).not.toBeNull();
-    expect(dockerCommand.replace(/\\(\w)/g, '/$1')).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('supports global mode', async () => {
     platform.getFile.mockResolvedValueOnce('Current composer.lock');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('New composer.lock' as any);
     expect(
       await composer.updateArtifacts('composer.json', [], '{}', {
@@ -129,6 +125,7 @@ describe('.updateArtifacts()', () => {
         binarySource: 'global',
       })
     ).not.toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('catches errors', async () => {
     platform.getFile.mockResolvedValueOnce('Current composer.lock');
diff --git a/test/manager/gomod/__snapshots__/artifacts.spec.ts.snap b/test/manager/gomod/__snapshots__/artifacts.spec.ts.snap
index 077ed134ea3e432100e1b17a12c394e691579f41..972d4c7bb3170c6dc0501894a9f5fc6c9d69bc8c 100644
--- a/test/manager/gomod/__snapshots__/artifacts.spec.ts.snap
+++ b/test/manager/gomod/__snapshots__/artifacts.spec.ts.snap
@@ -11,8 +11,141 @@ Array [
 ]
 `;
 
-exports[`.updateArtifacts() supports docker mode with credentials 1`] = `"docker run --rm -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GONOSUMDB -e CGO_ENABLED=0 -w \\"/tmp/github/some/repo\\" renovate/go bash -c \\"git config --global url.\\\\\\"https://some-token@github.com/\\\\\\".insteadOf \\\\\\"https://github.com/\\\\\\" && go get -d ./...\\""`;
+exports[`.updateArtifacts() catches errors 2`] = `Array []`;
 
-exports[`.updateArtifacts() supports docker mode with credentials, appMode and trustLevel=high 1`] = `"docker run --rm -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GONOSUMDB -e CGO_ENABLED=0 -w \\"/tmp/github/some/repo\\" renovate/go bash -c \\"git config --global url.\\\\\\"https://x-access-token:some-token@github.com/\\\\\\".insteadOf \\\\\\"https://github.com/\\\\\\" && go get -d ./...\\""`;
+exports[`.updateArtifacts() returns if no go.sum found 1`] = `Array []`;
 
-exports[`.updateArtifacts() supports docker mode without credentials 1`] = `"docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GONOSUMDB -e CGO_ENABLED=0 -w \\"/tmp/github/some/repo\\" renovate/go go get -d ./..."`;
+exports[`.updateArtifacts() returns null if unchanged 1`] = `
+Array [
+  Object {
+    "cmd": "go get -d ./...",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "GOPATH": "/tmp/renovate/cache/others/go",
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns updated go.sum 1`] = `
+Array [
+  Object {
+    "cmd": "go get -d ./...",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "GOPATH": "/tmp/renovate/cache/others/go",
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() supports docker mode with credentials 1`] = `
+Array [
+  Object {
+    "cmd": "docker run --rm -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GONOSUMDB -e CGO_ENABLED=0 -w \\"/tmp/github/some/repo\\" renovate/go bash -c \\"git config --global url.\\\\\\"https://some-token@github.com/\\\\\\".insteadOf \\\\\\"https://github.com/\\\\\\" && go get -d ./...\\"",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "GOPATH": "/tmp/renovate/cache/others/go",
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() supports docker mode with credentials and appMode enabled 1`] = `
+Array [
+  Object {
+    "cmd": "docker run --rm -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GONOSUMDB -e CGO_ENABLED=0 -w \\"/tmp/github/some/repo\\" renovate/go bash -c \\"git config --global url.\\\\\\"https://x-access-token:some-token@github.com/\\\\\\".insteadOf \\\\\\"https://github.com/\\\\\\" && go get -d ./...\\"",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "GOPATH": "/tmp/renovate/cache/others/go",
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+  Object {
+    "cmd": "docker run --rm -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GONOSUMDB -e CGO_ENABLED=0 -w \\"/tmp/github/some/repo\\" renovate/go bash -c \\"git config --global url.\\\\\\"https://x-access-token:some-token@github.com/\\\\\\".insteadOf \\\\\\"https://github.com/\\\\\\" && go mod tidy\\"",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "GOPATH": "/tmp/renovate/cache/others/go",
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() supports docker mode without credentials 1`] = `
+Array [
+  Object {
+    "cmd": "docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GONOSUMDB -e CGO_ENABLED=0 -w \\"/tmp/github/some/repo\\" renovate/go go get -d ./...",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "GOPATH": "/tmp/renovate/cache/others/go",
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() supports global mode 1`] = `
+Array [
+  Object {
+    "cmd": "go get -d ./...",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "GOPATH": "/tmp/renovate/cache/others/go",
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
diff --git a/test/manager/gomod/artifacts.spec.ts b/test/manager/gomod/artifacts.spec.ts
index b0b7cd0b367ea514b7a94325947810faa07acc87..d6be7fa1cce947f3b615f30f44c6aa88edcae68c 100644
--- a/test/manager/gomod/artifacts.spec.ts
+++ b/test/manager/gomod/artifacts.spec.ts
@@ -4,6 +4,7 @@ import * as gomod from '../../../lib/manager/gomod/artifacts';
 import { platform as _platform } from '../../../lib/platform';
 import { mocked } from '../../util';
 import { StatusResult } from '../../../lib/platform/git/storage';
+import { mockExecAll } from '../../execUtil';
 
 jest.mock('fs-extra');
 jest.mock('child_process');
@@ -32,38 +33,46 @@ const config = {
   cacheDir: '/tmp/renovate/cache',
 };
 
+let processEnv;
+
 describe('.updateArtifacts()', () => {
   beforeEach(() => {
     jest.resetAllMocks();
-    exec.mockImplementation((_cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    jest.resetModules();
+
+    processEnv = process.env;
+    process.env = {
+      HTTP_PROXY: 'http://example.com',
+      HTTPS_PROXY: 'https://example.com',
+      NO_PROXY: 'localhost',
+      HOME: '/home/user',
+      PATH: '/tmp/path',
+    };
+  });
+  afterEach(() => {
+    process.env = processEnv;
   });
   it('returns if no go.sum found', async () => {
+    const execSnapshots = mockExecAll(exec);
     expect(
       await gomod.updateArtifacts('go.mod', [], gomod1, config)
     ).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('returns null if unchanged', async () => {
     platform.getFile.mockResolvedValueOnce('Current go.sum');
-    exec.mockImplementationOnce((_cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     platform.getRepoStatus.mockResolvedValueOnce({
       modified: [],
     } as StatusResult);
     expect(
       await gomod.updateArtifacts('go.mod', [], gomod1, config)
     ).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('returns updated go.sum', async () => {
     platform.getFile.mockResolvedValueOnce('Current go.sum');
-    exec.mockImplementationOnce((_cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     platform.getRepoStatus.mockResolvedValueOnce({
       modified: ['go.sum'],
     } as StatusResult);
@@ -71,15 +80,11 @@ describe('.updateArtifacts()', () => {
     expect(
       await gomod.updateArtifacts('go.mod', [], gomod1, config)
     ).not.toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('supports docker mode without credentials', async () => {
     platform.getFile.mockResolvedValueOnce('Current go.sum');
-    let dockerCommand = null;
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      dockerCommand = cmd;
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     platform.getRepoStatus.mockResolvedValueOnce({
       modified: ['go.sum'],
     } as StatusResult);
@@ -91,14 +96,11 @@ describe('.updateArtifacts()', () => {
         dockerUser: 'foobar',
       })
     ).not.toBeNull();
-    expect(dockerCommand.replace(/\\(\w)/g, '/$1')).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('supports global mode', async () => {
     platform.getFile.mockResolvedValueOnce('Current go.sum');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     platform.getRepoStatus.mockResolvedValueOnce({
       modified: ['go.sum'],
     } as StatusResult);
@@ -109,18 +111,14 @@ describe('.updateArtifacts()', () => {
         binarySource: 'global',
       })
     ).not.toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('supports docker mode with credentials', async () => {
     hostRules.find.mockReturnValueOnce({
       token: 'some-token',
     });
     platform.getFile.mockResolvedValueOnce('Current go.sum');
-    let dockerCommand = null;
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      dockerCommand = cmd;
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     platform.getRepoStatus.mockResolvedValueOnce({
       modified: ['go.sum'],
     } as StatusResult);
@@ -131,19 +129,14 @@ describe('.updateArtifacts()', () => {
         binarySource: 'docker',
       })
     ).not.toBeNull();
-    expect(dockerCommand.replace(/\\(\w)/g, '/$1')).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
   });
-  it('supports docker mode with credentials, appMode and trustLevel=high', async () => {
+  it('supports docker mode with credentials and appMode enabled', async () => {
     hostRules.find.mockReturnValueOnce({
       token: 'some-token',
     });
     platform.getFile.mockResolvedValueOnce('Current go.sum');
-    let dockerCommand = null;
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      dockerCommand = cmd;
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     platform.getRepoStatus.mockResolvedValueOnce({
       modified: ['go.sum'],
     } as StatusResult);
@@ -152,7 +145,6 @@ describe('.updateArtifacts()', () => {
     fs.readFile.mockResolvedValueOnce('New go.sum 3' as any);
     try {
       global.appMode = true;
-      global.trustLevel = 'high';
       expect(
         await gomod.updateArtifacts('go.mod', [], gomod1, {
           ...config,
@@ -160,13 +152,13 @@ describe('.updateArtifacts()', () => {
           postUpdateOptions: ['gomodTidy'],
         })
       ).not.toBeNull();
-      expect(dockerCommand.replace(/\\(\w)/g, '/$1')).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     } finally {
       delete global.appMode;
-      delete global.trustLevel;
     }
   });
   it('catches errors', async () => {
+    const execSnapshots = mockExecAll(exec);
     platform.getFile.mockResolvedValueOnce('Current go.sum');
     fs.outputFile.mockImplementationOnce(() => {
       throw new Error('This update totally doesnt work');
@@ -174,5 +166,6 @@ describe('.updateArtifacts()', () => {
     expect(
       await gomod.updateArtifacts('go.mod', [], gomod1, config)
     ).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
   });
 });
diff --git a/test/manager/gradle/__snapshots__/index.spec.ts.snap b/test/manager/gradle/__snapshots__/index.spec.ts.snap
index 15f9f422b2f672116eb9c2cf8c085d36ab22f9a6..bc48a849d149a05183a139867f869b5c014cccb8 100644
--- a/test/manager/gradle/__snapshots__/index.spec.ts.snap
+++ b/test/manager/gradle/__snapshots__/index.spec.ts.snap
@@ -2,31 +2,66 @@
 
 exports[`manager/gradle extractPackageFile should configure the renovate report plugin 1`] = `
 Array [
-  "./gradlew --init-script renovate-plugin.gradle renovate",
+  Object {
+    "cmd": "./gradlew --init-script renovate-plugin.gradle renovate",
+    "options": Object {
+      "cwd": "localDir",
+      "encoding": "utf-8",
+      "timeout": 20000,
+    },
+  },
 ]
 `;
 
 exports[`manager/gradle extractPackageFile should execute gradlew when available 1`] = `
 Array [
-  "./gradlew --init-script renovate-plugin.gradle renovate",
+  Object {
+    "cmd": "./gradlew --init-script renovate-plugin.gradle renovate",
+    "options": Object {
+      "cwd": "localDir",
+      "encoding": "utf-8",
+      "timeout": 20000,
+    },
+  },
 ]
 `;
 
 exports[`manager/gradle extractPackageFile should return empty if renovate report is invalid 1`] = `
 Array [
-  "./gradlew --init-script renovate-plugin.gradle renovate",
+  Object {
+    "cmd": "./gradlew --init-script renovate-plugin.gradle renovate",
+    "options": Object {
+      "cwd": "localDir",
+      "encoding": "utf-8",
+      "timeout": 20000,
+    },
+  },
 ]
 `;
 
 exports[`manager/gradle extractPackageFile should return empty if there are no dependencies 1`] = `
 Array [
-  "./gradlew --init-script renovate-plugin.gradle renovate",
+  Object {
+    "cmd": "./gradlew --init-script renovate-plugin.gradle renovate",
+    "options": Object {
+      "cwd": "localDir",
+      "encoding": "utf-8",
+      "timeout": 20000,
+    },
+  },
 ]
 `;
 
 exports[`manager/gradle extractPackageFile should return empty if there is no dependency report 1`] = `
 Array [
-  "gradle --init-script renovate-plugin.gradle renovate",
+  Object {
+    "cmd": "gradle --init-script renovate-plugin.gradle renovate",
+    "options": Object {
+      "cwd": "localDir",
+      "encoding": "utf-8",
+      "timeout": 20000,
+    },
+  },
 ]
 `;
 
@@ -155,7 +190,14 @@ Array [
 
 exports[`manager/gradle extractPackageFile should return gradle dependencies 2`] = `
 Array [
-  "./gradlew --init-script renovate-plugin.gradle renovate",
+  Object {
+    "cmd": "./gradlew --init-script renovate-plugin.gradle renovate",
+    "options": Object {
+      "cwd": "localDir",
+      "encoding": "utf-8",
+      "timeout": 20000,
+    },
+  },
 ]
 `;
 
@@ -225,7 +267,14 @@ Array [
 
 exports[`manager/gradle extractPackageFile should return gradle dependencies for build.gradle in subdirectories if there is gradlew in the same directory 2`] = `
 Array [
-  "./gradlew --init-script renovate-plugin.gradle renovate",
+  Object {
+    "cmd": "./gradlew --init-script renovate-plugin.gradle renovate",
+    "options": Object {
+      "cwd": "localDir/foo",
+      "encoding": "utf-8",
+      "timeout": 20000,
+    },
+  },
 ]
 `;
 
@@ -354,7 +403,14 @@ Array [
 
 exports[`manager/gradle extractPackageFile should return gradle.kts dependencies 2`] = `
 Array [
-  "./gradlew --init-script renovate-plugin.gradle renovate",
+  Object {
+    "cmd": "./gradlew --init-script renovate-plugin.gradle renovate",
+    "options": Object {
+      "cwd": "localDir",
+      "encoding": "utf-8",
+      "timeout": 20000,
+    },
+  },
 ]
 `;
 
@@ -362,23 +418,55 @@ exports[`manager/gradle extractPackageFile should return null and gradle should
 
 exports[`manager/gradle extractPackageFile should run gradlew through \`sh\` when available but not executable 1`] = `
 Array [
-  "sh gradlew --init-script renovate-plugin.gradle renovate",
+  Object {
+    "cmd": "sh gradlew --init-script renovate-plugin.gradle renovate",
+    "options": Object {
+      "cwd": "localDir",
+      "encoding": "utf-8",
+      "timeout": 20000,
+    },
+  },
 ]
 `;
 
 exports[`manager/gradle extractPackageFile should throw registry failure if gradle execution fails 1`] = `[Error: registry-failure]`;
 
-exports[`manager/gradle extractPackageFile should throw registry failure if gradle execution fails 2`] = `Array []`;
+exports[`manager/gradle extractPackageFile should throw registry failure if gradle execution fails 2`] = `
+Array [
+  Object {
+    "cmd": "./gradlew --init-script renovate-plugin.gradle renovate",
+    "options": Object {
+      "cwd": "localDir",
+      "encoding": "utf-8",
+      "timeout": 20000,
+    },
+  },
+]
+`;
 
 exports[`manager/gradle extractPackageFile should use docker even if gradlew is available 1`] = `
 Array [
-  "docker run --rm -v \\"localDir\\":\\"localDir\\" -w \\"localDir\\" renovate/gradle gradle --init-script renovate-plugin.gradle renovate",
+  Object {
+    "cmd": "docker run --rm -v \\"localDir\\":\\"localDir\\" -w \\"localDir\\" renovate/gradle gradle --init-script renovate-plugin.gradle renovate",
+    "options": Object {
+      "cwd": "localDir",
+      "encoding": "utf-8",
+      "timeout": null,
+    },
+  },
 ]
 `;
 
 exports[`manager/gradle extractPackageFile should use docker if required 1`] = `
 Array [
-  "docker run --rm -v \\"localDir\\":\\"localDir\\" -w \\"localDir\\" renovate/gradle gradle --init-script renovate-plugin.gradle renovate",
+  Object {
+    "cmd": "docker run --rm -v \\"localDir\\":\\"localDir\\" -w \\"localDir\\" renovate/gradle gradle --init-script renovate-plugin.gradle renovate",
+    "options": Object {
+      "cwd": "localDir",
+      "encoding": "utf-8",
+      "timeout": 20000,
+    },
+  },
 ]
 `;
 
@@ -425,7 +513,14 @@ Array [
 
 exports[`manager/gradle extractPackageFile should use repositories only for current project 2`] = `
 Array [
-  "./gradlew --init-script renovate-plugin.gradle renovate",
+  Object {
+    "cmd": "./gradlew --init-script renovate-plugin.gradle renovate",
+    "options": Object {
+      "cwd": "localDir",
+      "encoding": "utf-8",
+      "timeout": 20000,
+    },
+  },
 ]
 `;
 
diff --git a/test/manager/gradle/index.spec.ts b/test/manager/gradle/index.spec.ts
index 0cd795a18bf65d99100258b8261b77bb2d319734..2a68572dc8022f248bdd5fcd7066a45050f5e137 100644
--- a/test/manager/gradle/index.spec.ts
+++ b/test/manager/gradle/index.spec.ts
@@ -4,6 +4,7 @@ import fsReal from 'fs';
 import { exec as _exec } from 'child_process';
 import * as manager from '../../../lib/manager/gradle';
 import { platform as _platform, Platform } from '../../../lib/platform';
+import { mockExecAll } from '../../execUtil';
 
 jest.mock('fs-extra');
 jest.mock('child_process');
@@ -19,61 +20,67 @@ const config = {
   },
 };
 
+let processEnv;
+
 const updatesDependenciesReport = fsReal.readFileSync(
   'test/datasource/gradle/_fixtures/updatesReport.json',
   'utf8'
 );
 
+const gradleOutput = {
+  stdout: 'gradle output',
+  stderr: '',
+};
+
 describe('manager/gradle', () => {
   beforeEach(() => {
     jest.resetAllMocks();
+    jest.resetModules();
+
     fs.readFile.mockResolvedValue(updatesDependenciesReport as any);
     fs.mkdir.mockResolvedValue();
     fs.exists.mockResolvedValue(true);
     fs.access.mockResolvedValue(undefined);
     platform.getFile.mockResolvedValue('some content');
+
+    processEnv = process.env;
+    process.env = {
+      HTTP_PROXY: 'http://example.com',
+      HTTPS_PROXY: 'https://example.com',
+      NO_PROXY: 'localhost',
+      HOME: '/home/user',
+      PATH: '/tmp/path',
+    };
+  });
+  afterEach(() => {
+    process.env = processEnv;
   });
 
   describe('extractPackageFile', () => {
     it('should return gradle dependencies', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       const dependencies = await manager.extractAllPackageFiles(config, [
         'build.gradle',
         'subproject/build.gradle',
       ]);
       expect(dependencies).toMatchSnapshot();
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should return gradle.kts dependencies', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       const dependencies = await manager.extractAllPackageFiles(config, [
         'build.gradle.kts',
         'subproject/build.gradle.kts',
       ]);
       expect(dependencies).toMatchSnapshot();
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should return empty if there are no dependencies', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       fs.readFile.mockResolvedValue(fsReal.readFileSync(
         'test/datasource/gradle/_fixtures/updatesReportEmpty.json',
@@ -84,28 +91,20 @@ describe('manager/gradle', () => {
       ]);
 
       expect(dependencies).toEqual([]);
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should throw registry failure if gradle execution fails', async () => {
-      const commands = [];
-      exec.mockImplementation((cmd, _options, _callback) => {
-        throw new Error();
-      });
+      const execSnapshots = mockExecAll(exec, new Error());
 
       await expect(
         manager.extractAllPackageFiles(config, ['build.gradle'])
       ).rejects.toMatchSnapshot();
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should return empty if there is no dependency report', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       fs.exists.mockResolvedValue(false);
       const dependencies = await manager.extractAllPackageFiles(config, [
@@ -113,16 +112,11 @@ describe('manager/gradle', () => {
       ]);
 
       expect(dependencies).toEqual([]);
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should return empty if renovate report is invalid', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       const renovateReport = `
         Invalid JSON]
@@ -133,16 +127,11 @@ describe('manager/gradle', () => {
         'build.gradle',
       ]);
       expect(dependencies).toEqual([]);
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should use repositories only for current project', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       const multiProjectUpdatesReport = fsReal.readFileSync(
         'test/datasource/gradle/_fixtures/MultiProjectUpdatesReport.json',
@@ -154,16 +143,11 @@ describe('manager/gradle', () => {
         'build.gradle',
       ]);
       expect(dependencies).toMatchSnapshot();
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should execute gradlew when available', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       await manager.extractAllPackageFiles(config, ['build.gradle']);
 
@@ -174,16 +158,11 @@ describe('manager/gradle', () => {
         cwd: 'localDir',
         timeout: 20000,
       });
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should run gradlew through `sh` when available but not executable', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       fs.access.mockRejectedValue(undefined);
       await manager.extractAllPackageFiles(config, ['build.gradle']);
@@ -195,16 +174,11 @@ describe('manager/gradle', () => {
         cwd: 'localDir',
         timeout: 20000,
       });
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should return null and gradle should not be executed if no root build.gradle', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       fs.exists.mockResolvedValue(false);
 
@@ -214,47 +188,32 @@ describe('manager/gradle', () => {
       ).toBeNull();
 
       expect(exec).toHaveBeenCalledTimes(0);
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should return gradle dependencies for build.gradle in subdirectories if there is gradlew in the same directory', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       const dependencies = await manager.extractAllPackageFiles(config, [
         'foo/build.gradle',
       ]);
       expect(dependencies).toMatchSnapshot();
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should configure the renovate report plugin', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       await manager.extractAllPackageFiles(config, ['build.gradle']);
 
       expect(toUnix(fs.writeFile.mock.calls[0][0] as string)).toBe(
         'localDir/renovate-plugin.gradle'
       );
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should use docker if required', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       const configWithDocker = {
         binarySource: 'docker',
@@ -263,16 +222,11 @@ describe('manager/gradle', () => {
       await manager.extractAllPackageFiles(configWithDocker, ['build.gradle']);
 
       expect(exec.mock.calls[0][0].includes('docker run')).toBe(true);
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should use docker even if gradlew is available', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       const configWithDocker = {
         binarySource: 'docker',
@@ -282,18 +236,13 @@ describe('manager/gradle', () => {
       await manager.extractAllPackageFiles(configWithDocker, ['build.gradle']);
 
       expect(exec.mock.calls[0][0].includes('docker run')).toBe(true);
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
   });
 
   describe('updateDependency', () => {
     it('should update an existing module dependency', () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       const buildGradleContent = fsReal.readFileSync(
         'test/datasource/gradle/_fixtures/build.gradle.example1',
@@ -313,16 +262,11 @@ describe('manager/gradle', () => {
       expect(buildGradleContentUpdated).toMatch('cglib:cglib-nodep:3.2.8');
       expect(buildGradleContentUpdated).not.toMatch('cglib:cglib-nodep:3.1');
 
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should update an existing plugin dependency', () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       const buildGradleContent = `
         plugins {
@@ -351,16 +295,11 @@ describe('manager/gradle', () => {
         'id "com.github.ben-manes.versions" version "0.20.0"'
       );
 
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
 
     it('should update an existing plugin dependency with Kotlin DSL', () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'gradle output', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecAll(exec, gradleOutput);
 
       const buildGradleContent = `
         plugins {
@@ -389,7 +328,7 @@ describe('manager/gradle', () => {
         'id("com.github.ben-manes.versions") version "0.20.0"'
       );
 
-      expect(commands.map(x => x.replace(/\\(\w)/g, '/$1'))).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
   });
 });
diff --git a/test/manager/mix/__snapshots__/artifacts.spec.ts.snap b/test/manager/mix/__snapshots__/artifacts.spec.ts.snap
index 40d412327800ebf75a5daf2a26c87eba02979acc..c054de2c7672a1af774c8b8ec63715d98777a3ee 100644
--- a/test/manager/mix/__snapshots__/artifacts.spec.ts.snap
+++ b/test/manager/mix/__snapshots__/artifacts.spec.ts.snap
@@ -11,6 +11,18 @@ Array [
 ]
 `;
 
+exports[`.updateArtifacts() returns null if unchanged 1`] = `
+Array [
+  Object {
+    "cmd": "mix deps.update plug",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+    },
+  },
+]
+`;
+
 exports[`.updateArtifacts() returns updated mix.lock 1`] = `
 Array [
   Object {
@@ -22,4 +34,14 @@ Array [
 ]
 `;
 
-exports[`.updateArtifacts() returns updated mix.lock 2`] = `"docker run --rm -v /tmp/github/some/repo:/tmp/github/some/repo -w /tmp/github/some/repo renovate/mix mix deps.update plug"`;
+exports[`.updateArtifacts() returns updated mix.lock 2`] = `
+Array [
+  Object {
+    "cmd": "docker run --rm -v /tmp/github/some/repo:/tmp/github/some/repo -w /tmp/github/some/repo renovate/mix mix deps.update plug",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+    },
+  },
+]
+`;
diff --git a/test/manager/mix/artifacts.spec.ts b/test/manager/mix/artifacts.spec.ts
index 351c7b9d6357889289a1bcf47d2734e76d90b201..fee2cfd0e5a9a8b703bfb13879d3c7ec09be3067 100644
--- a/test/manager/mix/artifacts.spec.ts
+++ b/test/manager/mix/artifacts.spec.ts
@@ -3,6 +3,7 @@ import { exec as _exec } from 'child_process';
 import { platform as _platform } from '../../../lib/platform';
 import { updateArtifacts } from '../../../lib/manager/mix';
 import { mocked } from '../../util';
+import { mockExecAll } from '../../execUtil';
 
 const fs: jest.Mocked<typeof _fs> = _fs as any;
 const exec: jest.Mock<typeof _exec> = _exec as any;
@@ -16,9 +17,24 @@ const config = {
   localDir: '/tmp/github/some/repo',
 };
 
+let processEnv;
+
 describe('.updateArtifacts()', () => {
   beforeEach(() => {
     jest.resetAllMocks();
+    jest.resetModules();
+
+    processEnv = process.env;
+    process.env = {
+      HTTP_PROXY: 'http://example.com',
+      HTTPS_PROXY: 'https://example.com',
+      NO_PROXY: 'localhost',
+      HOME: '/home/user',
+      PATH: '/tmp/path',
+    };
+  });
+  afterEach(() => {
+    process.env = processEnv;
   });
   it('returns null if no mix.lock found', async () => {
     expect(await updateArtifacts('mix.exs', ['plug'], '', config)).toBeNull();
@@ -39,21 +55,14 @@ describe('.updateArtifacts()', () => {
   });
   it('returns null if unchanged', async () => {
     platform.getFile.mockResolvedValueOnce('Current mix.lock');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockResolvedValueOnce('Current mix.lock' as any);
     expect(await updateArtifacts('mix.exs', ['plug'], '', config)).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('returns updated mix.lock', async () => {
     platform.getFile.mockResolvedValueOnce('Old mix.lock');
-    let dockerCommand = null;
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      dockerCommand = cmd;
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockResolvedValueOnce('New mix.lock' as any);
     expect(
       await updateArtifacts('mix.exs', ['plug'], '{}', {
@@ -61,7 +70,7 @@ describe('.updateArtifacts()', () => {
         binarySource: 'docker',
       })
     ).toMatchSnapshot();
-    expect(dockerCommand.replace(/\\(\w)/g, '/$1')).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('catches errors', async () => {
     platform.getFile.mockResolvedValueOnce('Current mix.lock');
diff --git a/test/manager/pip_setup/__snapshots__/extract.spec.ts.snap b/test/manager/pip_setup/__snapshots__/extract.spec.ts.snap
index edf76c1da3ae9f65c07372fb33389d3c8bc7dc7f..d7b56fcb7d751b484a2aa1fbf7334677d67ba411 100644
--- a/test/manager/pip_setup/__snapshots__/extract.spec.ts.snap
+++ b/test/manager/pip_setup/__snapshots__/extract.spec.ts.snap
@@ -4,8 +4,23 @@ exports[`lib/manager/pip_setup/extract getPythonAlias returns the python alias t
 
 exports[`lib/manager/pip_setup/extract getPythonAlias returns the python alias to use 2`] = `
 Array [
-  "python --version",
-  "python3 --version",
-  "python3.8 --version",
+  Object {
+    "cmd": "python --version",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "python3 --version",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "python3.8 --version",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
 ]
 `;
diff --git a/test/manager/pip_setup/__snapshots__/index.spec.ts.snap b/test/manager/pip_setup/__snapshots__/index.spec.ts.snap
index 1b142057153b55428bd7250f465bf14ad2aba1c2..50e6efd331cba1dcea4116a4df53a9c3f89eda9e 100644
--- a/test/manager/pip_setup/__snapshots__/index.spec.ts.snap
+++ b/test/manager/pip_setup/__snapshots__/index.spec.ts.snap
@@ -1,5 +1,164 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`lib/manager/pip_setup/index extractPackageFile() catches error 1`] = `
+Array [
+  Object {
+    "cmd": "python --version",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "python3 --version",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "python3.8 --version",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "<extract.py> \\"/tmp/folders/foobar.py\\"",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "timeout": 5000,
+    },
+  },
+]
+`;
+
+exports[`lib/manager/pip_setup/index extractPackageFile() returns found deps (docker) 1`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": ">=3.1.13.0,<5.0",
+      "datasource": "pypi",
+      "depName": "celery",
+      "managerData": Object {
+        "lineNumber": 36,
+      },
+    },
+    Object {
+      "currentValue": ">=1.7",
+      "datasource": "pypi",
+      "depName": "logging_tree",
+      "managerData": Object {
+        "lineNumber": 39,
+      },
+    },
+    Object {
+      "currentValue": ">=2.2",
+      "datasource": "pypi",
+      "depName": "pygments",
+      "managerData": Object {
+        "lineNumber": 40,
+      },
+    },
+    Object {
+      "currentValue": ">=5.0",
+      "datasource": "pypi",
+      "depName": "psutil",
+      "managerData": Object {
+        "lineNumber": 41,
+      },
+    },
+    Object {
+      "currentValue": ">=3.0",
+      "datasource": "pypi",
+      "depName": "objgraph",
+      "managerData": Object {
+        "lineNumber": 42,
+      },
+    },
+    Object {
+      "currentValue": ">=1.11.23,<2.0",
+      "datasource": "pypi",
+      "depName": "django",
+      "managerData": Object {
+        "lineNumber": 45,
+      },
+    },
+    Object {
+      "currentValue": ">=0.11,<2.0",
+      "datasource": "pypi",
+      "depName": "flask",
+      "managerData": Object {
+        "lineNumber": 48,
+      },
+    },
+    Object {
+      "currentValue": ">=1.4,<2.0",
+      "datasource": "pypi",
+      "depName": "blinker",
+      "managerData": Object {
+        "lineNumber": 49,
+      },
+    },
+    Object {
+      "currentValue": ">=19.7.0,<20.0",
+      "datasource": "pypi",
+      "depName": "gunicorn",
+      "managerData": Object {
+        "lineNumber": 61,
+      },
+    },
+    Object {
+      "currentValue": ">=3.2.1,<4.0",
+      "datasource": "pypi",
+      "depName": "statsd",
+      "managerData": Object {
+        "lineNumber": 62,
+      },
+    },
+    Object {
+      "currentValue": ">=0.15.3,<0.16",
+      "datasource": "pypi",
+      "depName": "Werkzeug",
+      "managerData": Object {
+        "lineNumber": 62,
+      },
+    },
+    Object {
+      "currentValue": ">=2.10.0,<3.0",
+      "datasource": "pypi",
+      "depName": "requests",
+      "managerData": Object {
+        "lineNumber": 63,
+      },
+      "skipReason": "ignored",
+    },
+    Object {
+      "currentValue": ">=5.27.1,<7.0",
+      "datasource": "pypi",
+      "depName": "raven",
+      "managerData": Object {
+        "lineNumber": 64,
+      },
+    },
+    Object {
+      "currentValue": ">=0.15.2,<0.17",
+      "datasource": "pypi",
+      "depName": "future",
+      "managerData": Object {
+        "lineNumber": 65,
+      },
+    },
+    Object {
+      "currentValue": ">=1.0.16,<2.0",
+      "datasource": "pypi",
+      "depName": "ipaddress",
+      "managerData": Object {
+        "lineNumber": 66,
+      },
+    },
+  ],
+}
+`;
+
 exports[`lib/manager/pip_setup/index extractPackageFile() returns found deps 1`] = `
 Object {
   "deps": Array [
@@ -127,3 +286,65 @@ Object {
   ],
 }
 `;
+
+exports[`lib/manager/pip_setup/index extractPackageFile() returns found deps 2`] = `
+Array [
+  Object {
+    "cmd": "python --version",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "python3 --version",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "python3.8 --version",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "<extract.py> \\"test/manager/pip_setup/_fixtures/setup.py\\"",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "timeout": 5000,
+    },
+  },
+]
+`;
+
+exports[`lib/manager/pip_setup/index extractPackageFile() should return null for invalid file 1`] = `
+Array [
+  Object {
+    "cmd": "python --version",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "python3 --version",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "python3.8 --version",
+    "options": Object {
+      "encoding": "utf-8",
+    },
+  },
+  Object {
+    "cmd": "<extract.py> \\"/tmp/folders/foobar.py\\"",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "timeout": 5000,
+    },
+  },
+]
+`;
diff --git a/test/manager/pip_setup/extract.spec.ts b/test/manager/pip_setup/extract.spec.ts
index 49093ec7ba2574aa68e010da7e9e4601a64f3800..8504e422400ca726d12a64ff27ce589f2e8fc38a 100644
--- a/test/manager/pip_setup/extract.spec.ts
+++ b/test/manager/pip_setup/extract.spec.ts
@@ -6,15 +6,30 @@ import {
   getPythonAlias,
   pythonVersions,
 } from '../../../lib/manager/pip_setup/extract';
+import { mockExecSequence } from '../../execUtil';
 
 const exec: jest.Mock<typeof _exec> = _exec as any;
 jest.mock('child_process');
 
+let processEnv;
+
 describe('lib/manager/pip_setup/extract', () => {
   beforeEach(() => {
     jest.resetAllMocks();
     jest.resetModules();
     resetModule();
+
+    processEnv = process.env;
+    process.env = {
+      HTTP_PROXY: 'http://example.com',
+      HTTPS_PROXY: 'https://example.com',
+      NO_PROXY: 'localhost',
+      HOME: '/home/user',
+      PATH: '/tmp/path',
+    };
+  });
+  afterEach(() => {
+    process.env = processEnv;
   });
   describe('parsePythonVersion', () => {
     it('returns major and minor version numbers', () => {
@@ -23,25 +38,15 @@ describe('lib/manager/pip_setup/extract', () => {
   });
   describe('getPythonAlias', () => {
     it('returns the python alias to use', async () => {
-      const commands = [];
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: '', stderr: 'Python 2.7.17\\n' });
-        return undefined;
-      });
-      exec.mockImplementationOnce((cmd, _options, _callback) => {
-        commands.push(cmd);
-        throw new Error();
-      });
-      exec.mockImplementationOnce((cmd, _options, callback) => {
-        commands.push(cmd);
-        callback(null, { stdout: 'Python 3.8.0\\n', stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecSequence(exec, [
+        { stdout: '', stderr: 'Python 2.7.17\\n' },
+        new Error(),
+        { stdout: 'Python 3.8.0\\n', stderr: '' },
+      ]);
       const result = await getPythonAlias();
       expect(pythonVersions.includes(result)).toBe(true);
       expect(result).toMatchSnapshot();
-      expect(commands).toMatchSnapshot();
+      expect(execSnapshots).toMatchSnapshot();
     });
   });
   // describe('Test for presence of mock lib', () => {
diff --git a/test/manager/pip_setup/index.spec.ts b/test/manager/pip_setup/index.spec.ts
index 596f6b0e540c44b41ed4ea782f8db298b412d3c2..097bc5437b4ac7e45842faf66fa3243d81cefd8c 100644
--- a/test/manager/pip_setup/index.spec.ts
+++ b/test/manager/pip_setup/index.spec.ts
@@ -1,9 +1,8 @@
 import { readFileSync } from 'fs';
 import { exec as _exec } from 'child_process';
-import { file as _file } from 'tmp-promise';
-import { relative } from 'path';
 import * as extract from '../../../lib/manager/pip_setup/extract';
 import { extractPackageFile } from '../../../lib/manager/pip_setup';
+import { ExecSnapshots, mockExecAll, mockExecSequence } from '../../execUtil';
 
 const packageFile = 'test/manager/pip_setup/_fixtures/setup.py';
 const content = readFileSync(packageFile, 'utf8');
@@ -12,16 +11,26 @@ const packageFileJson = 'test/manager/pip_setup/_fixtures/setup.py.json';
 const jsonContent = readFileSync(packageFileJson, 'utf8');
 
 const config = {
-  localDir: '.',
+  localDir: '/tmp/github/some/repo',
 };
 
+let processEnv;
+
 const exec: jest.Mock<typeof _exec> = _exec as any;
 jest.mock('child_process');
 
-async function tmpFile() {
-  const file = await _file({ postfix: '.py' });
-  return relative('.', file.path);
-}
+const pythonVersionCallResults = [
+  { stdout: '', stderr: 'Python 2.7.17\\n' },
+  { stdout: 'Python 3.7.5\\n', stderr: '' },
+  new Error(),
+];
+
+// TODO: figure out snapshot similarity for each CI platform
+const fixSnapshots = (snapshots: ExecSnapshots): ExecSnapshots =>
+  snapshots.map(snapshot => ({
+    ...snapshot,
+    cmd: snapshot.cmd.replace(/^.*\/extract\.py"\s+/, '<extract.py> '),
+  }));
 
 describe('lib/manager/pip_setup/index', () => {
   describe('extractPackageFile()', () => {
@@ -30,49 +39,68 @@ describe('lib/manager/pip_setup/index', () => {
       jest.resetModules();
       extract.resetModule();
 
-      exec.mockImplementationOnce((_cmd, _options, callback) => {
-        callback(null, { stdout: '', stderr: 'Python 2.7.17\\n' });
-        return undefined;
-      });
-      exec.mockImplementationOnce((_cmd, _options, callback) => {
-        callback(null, { stdout: 'Python 3.7.5\\n', stderr: '' });
-        return undefined;
-      });
-      exec.mockImplementationOnce((_cmd, _options, _callback) => {
-        throw new Error();
-      });
+      processEnv = process.env;
+      process.env = {
+        HTTP_PROXY: 'http://example.com',
+        HTTPS_PROXY: 'https://example.com',
+        NO_PROXY: 'localhost',
+        HOME: '/home/user',
+        PATH: '/tmp/path',
+      };
+    });
+    afterEach(() => {
+      process.env = processEnv;
     });
     it('returns found deps', async () => {
-      exec.mockImplementationOnce((_cmd, _options, callback) => {
-        callback(null, { stdout: jsonContent, stderr: '' });
-        return undefined;
-      });
+      const execSnapshots = mockExecSequence(exec, [
+        ...pythonVersionCallResults,
+        { stdout: jsonContent, stderr: '' },
+      ]);
       expect(
         await extractPackageFile(content, packageFile, config)
       ).toMatchSnapshot();
       expect(exec).toHaveBeenCalledTimes(4);
+      expect(fixSnapshots(execSnapshots)).toMatchSnapshot();
     });
-    it('should return null for invalid file', async () => {
-      exec.mockImplementationOnce((_cmd, _options, _callback) => {
-        throw new Error();
-      });
+    it('returns found deps (docker)', async () => {
+      const execSnapshots = mockExecSequence(exec, [
+        { stdout: '', stderr: '' }, // docker pull
+        { stdout: jsonContent, stderr: '' },
+      ]);
 
       expect(
-        await extractPackageFile('raise Exception()', await tmpFile(), config)
+        await extractPackageFile(content, packageFile, {
+          ...config,
+          binarySource: 'docker',
+        })
+      ).toMatchSnapshot();
+      expect(execSnapshots).toHaveLength(2); // TODO: figure out volume arguments in Windows
+    });
+    it('should return null for invalid file', async () => {
+      const execSnapshots = mockExecSequence(exec, [
+        ...pythonVersionCallResults,
+        new Error(),
+      ]);
+      expect(
+        await extractPackageFile(
+          'raise Exception()',
+          '/tmp/folders/foobar.py',
+          config
+        )
       ).toBeNull();
       expect(exec).toHaveBeenCalledTimes(4);
+      expect(fixSnapshots(execSnapshots)).toMatchSnapshot();
     });
     it('catches error', async () => {
-      jest.resetAllMocks();
-      jest.resetModules();
-      extract.resetModule();
-      exec.mockImplementation((_cmd, _options, _callback) => {
-        throw new Error();
-      });
+      const execSnapshots = mockExecAll(exec, new Error());
       expect(
-        await extractPackageFile('raise Exception()', await tmpFile(), config)
+        await extractPackageFile(
+          'raise Exception()',
+          '/tmp/folders/foobar.py',
+          config
+        )
       ).toBeNull();
-      expect(exec).toHaveBeenCalledTimes(4);
+      expect(fixSnapshots(execSnapshots)).toMatchSnapshot();
     });
   });
   /*
diff --git a/test/manager/pipenv/__snapshots__/artifacts.spec.ts.snap b/test/manager/pipenv/__snapshots__/artifacts.spec.ts.snap
index 62306183153d4cec2c8ce1f6e9a6b2ba2fedd883..a03c90d07b2f844f76f67513c1e2a3dd6a0e8933 100644
--- a/test/manager/pipenv/__snapshots__/artifacts.spec.ts.snap
+++ b/test/manager/pipenv/__snapshots__/artifacts.spec.ts.snap
@@ -11,4 +11,62 @@ Array [
 ]
 `;
 
-exports[`.updateArtifacts() supports docker mode 1`] = `"docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache/others/pipenv\\":\\"/tmp/renovate/cache/others/pipenv\\" -e LC_ALL -e LANG -e PIPENV_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/pipenv pipenv lock"`;
+exports[`.updateArtifacts() returns null if unchanged 1`] = `
+Array [
+  Object {
+    "cmd": "pipenv lock",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+        "PIPENV_CACHE_DIR": "/tmp/renovate/cache/others/pipenv",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns updated Pipfile.lock 1`] = `
+Array [
+  Object {
+    "cmd": "pipenv lock",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+        "PIPENV_CACHE_DIR": "/tmp/renovate/cache/others/pipenv",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() supports docker mode 1`] = `
+Array [
+  Object {
+    "cmd": "docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache/others/pipenv\\":\\"/tmp/renovate/cache/others/pipenv\\" -e LC_ALL -e LANG -e PIPENV_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/pipenv pipenv lock",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+        "PIPENV_CACHE_DIR": "/tmp/renovate/cache/others/pipenv",
+      },
+    },
+  },
+]
+`;
diff --git a/test/manager/pipenv/artifacts.spec.ts b/test/manager/pipenv/artifacts.spec.ts
index 15f1de78491a47314090cea87b862152a36c6cc6..3c58aadb160f722dbd6711d5bba22710e8112636 100644
--- a/test/manager/pipenv/artifacts.spec.ts
+++ b/test/manager/pipenv/artifacts.spec.ts
@@ -4,6 +4,7 @@ import * as pipenv from '../../../lib/manager/pipenv/artifacts';
 import { platform as _platform } from '../../../lib/platform';
 import { mocked } from '../../util';
 import { StatusResult } from '../../../lib/platform/git/storage';
+import { mockExecAll } from '../../execUtil';
 
 jest.mock('fs-extra');
 jest.mock('child_process');
@@ -18,50 +19,50 @@ const config = {
   cacheDir: '/tmp/renovate/cache',
 };
 
+const processEnv = process.env;
+
 describe('.updateArtifacts()', () => {
   beforeEach(() => {
     jest.resetAllMocks();
+    process.env = {
+      HTTP_PROXY: 'http://example.com',
+      HTTPS_PROXY: 'https://example.com',
+      NO_PROXY: 'localhost',
+      HOME: '/home/user',
+      PATH: '/tmp/path',
+    };
   });
   afterEach(() => {
-    delete global.trustLevel;
+    process.env = processEnv;
   });
+
   it('returns if no Pipfile.lock found', async () => {
     expect(await pipenv.updateArtifacts('Pipfile', [], '', config)).toBeNull();
   });
   it('returns null if unchanged', async () => {
     platform.getFile.mockResolvedValueOnce('Current Pipfile.lock');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('Current Pipfile.lock' as any);
     expect(
       await pipenv.updateArtifacts('Pipfile', [], '{}', config)
     ).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('returns updated Pipfile.lock', async () => {
     platform.getFile.mockResolvedValueOnce('Current Pipfile.lock');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     platform.getRepoStatus.mockResolvedValue({
       modified: ['Pipfile.lock'],
     } as StatusResult);
     fs.readFile.mockReturnValueOnce('New Pipfile.lock' as any);
-    global.trustLevel = 'high';
     expect(
       await pipenv.updateArtifacts('Pipfile', [], '{}', config)
     ).not.toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('supports docker mode', async () => {
     platform.getFile.mockResolvedValueOnce('Current Pipfile.lock');
-    let dockerCommand = null;
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      dockerCommand = cmd;
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     platform.getRepoStatus.mockResolvedValue({
       modified: ['Pipfile.lock'],
     } as StatusResult);
@@ -73,7 +74,7 @@ describe('.updateArtifacts()', () => {
         dockerUser: 'foobar',
       })
     ).not.toBeNull();
-    expect(dockerCommand.replace(/\\(\w)/g, '/$1')).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('catches errors', async () => {
     platform.getFile.mockResolvedValueOnce('Current Pipfile.lock');
diff --git a/test/manager/poetry/__snapshots__/artifacts.spec.ts.snap b/test/manager/poetry/__snapshots__/artifacts.spec.ts.snap
index fc0d6ac02d138479652a520d56af9284371c284d..c093f262c65cdddfa78e62fc5f463a62607173d4 100644
--- a/test/manager/poetry/__snapshots__/artifacts.spec.ts.snap
+++ b/test/manager/poetry/__snapshots__/artifacts.spec.ts.snap
@@ -11,4 +11,59 @@ Array [
 ]
 `;
 
-exports[`.updateArtifacts() returns updated poetry.lock using docker 1`] = `"docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/poetry poetry update --lock --no-interaction dep1"`;
+exports[`.updateArtifacts() returns null if unchanged 1`] = `
+Array [
+  Object {
+    "cmd": "poetry update --lock --no-interaction dep1",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns updated poetry.lock 1`] = `
+Array [
+  Object {
+    "cmd": "poetry update --lock --no-interaction dep1",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
+
+exports[`.updateArtifacts() returns updated poetry.lock using docker 1`] = `
+Array [
+  Object {
+    "cmd": "docker run --rm --user=foobar -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/poetry poetry update --lock --no-interaction dep1",
+    "options": Object {
+      "cwd": "/tmp/github/some/repo",
+      "encoding": "utf-8",
+      "env": Object {
+        "HOME": "/home/user",
+        "HTTPS_PROXY": "https://example.com",
+        "HTTP_PROXY": "http://example.com",
+        "NO_PROXY": "localhost",
+        "PATH": "/tmp/path",
+      },
+    },
+  },
+]
+`;
diff --git a/test/manager/poetry/artifacts.spec.ts b/test/manager/poetry/artifacts.spec.ts
index 70df1be40e0a4f8761fc14dad25f056f3f1eb987..6c1e425edd8eb30b8adf54a2d3ee118237f65e45 100644
--- a/test/manager/poetry/artifacts.spec.ts
+++ b/test/manager/poetry/artifacts.spec.ts
@@ -3,6 +3,7 @@ import { exec as _exec } from 'child_process';
 import { updateArtifacts } from '../../../lib/manager/poetry/artifacts';
 import { platform as _platform } from '../../../lib/platform';
 import { mocked } from '../../util';
+import { mockExecAll } from '../../execUtil';
 
 jest.mock('fs-extra');
 jest.mock('child_process');
@@ -15,12 +16,21 @@ const config = {
   localDir: '/tmp/github/some/repo',
 };
 
+const processEnv = process.env;
+
 describe('.updateArtifacts()', () => {
   beforeEach(() => {
     jest.resetAllMocks();
+    process.env = {
+      HTTP_PROXY: 'http://example.com',
+      HTTPS_PROXY: 'https://example.com',
+      NO_PROXY: 'localhost',
+      HOME: '/home/user',
+      PATH: '/tmp/path',
+    };
   });
   afterEach(() => {
-    delete global.trustLevel;
+    process.env = processEnv;
   });
   it('returns null if no poetry.lock found', async () => {
     const updatedDeps = ['dep1'];
@@ -33,40 +43,29 @@ describe('.updateArtifacts()', () => {
   });
   it('returns null if unchanged', async () => {
     platform.getFile.mockResolvedValueOnce('Current poetry.lock');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('Current poetry.lock' as any);
     const updatedDeps = ['dep1'];
     expect(
       await updateArtifacts('pyproject.toml', updatedDeps, '', config)
     ).toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('returns updated poetry.lock', async () => {
     platform.getFile.mockResolvedValueOnce('Old poetry.lock');
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('New poetry.lock' as any);
     const updatedDeps = ['dep1'];
-    global.trustLevel = 'high';
     expect(
       await updateArtifacts('pyproject.toml', updatedDeps, '{}', config)
     ).not.toBeNull();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('returns updated poetry.lock using docker', async () => {
     platform.getFile.mockResolvedValueOnce('Old poetry.lock');
-    let dockerCommand = null;
-    exec.mockImplementationOnce((cmd, _options, callback) => {
-      dockerCommand = cmd;
-      callback(null, { stdout: '', stderr: '' });
-      return undefined;
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('New poetry.lock' as any);
     const updatedDeps = ['dep1'];
-    global.trustLevel = 'high';
     expect(
       await updateArtifacts('pyproject.toml', updatedDeps, '{}', {
         ...config,
@@ -74,7 +73,7 @@ describe('.updateArtifacts()', () => {
         dockerUser: 'foobar',
       })
     ).not.toBeNull();
-    expect(dockerCommand.replace(/\\(\w)/g, '/$1')).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('catches errors', async () => {
     platform.getFile.mockResolvedValueOnce('Current poetry.lock');
diff --git a/test/workers/branch/lock-files/__snapshots__/lerna.spec.ts.snap b/test/workers/branch/lock-files/__snapshots__/lerna.spec.ts.snap
new file mode 100644
index 0000000000000000000000000000000000000000..52b590332c64c5be3c0822d27bd055d5a6bc33de
--- /dev/null
+++ b/test/workers/branch/lock-files/__snapshots__/lerna.spec.ts.snap
@@ -0,0 +1,53 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`generateLockFiles() defaults to latest 1`] = `
+Array [
+  Object {
+    "cmd": "npm i -g -C ~/.npm/lerna@latest lerna@latest && npm install --package-lock-only --no-audit && ~/.npm/lerna@latest/bin/lerna bootstrap --no-ci -- --package-lock-only --no-audit",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": Object {},
+    },
+  },
+]
+`;
+
+exports[`generateLockFiles() generates package-lock.json files 1`] = `
+Array [
+  Object {
+    "cmd": "npm i -g -C ~/.npm/lerna@2.0.0 lerna@2.0.0 && npm install --package-lock-only --no-audit && ~/.npm/lerna@2.0.0/bin/lerna bootstrap --no-ci -- --package-lock-only --no-audit",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": Object {},
+    },
+  },
+]
+`;
+
+exports[`generateLockFiles() generates yarn.lock files 1`] = `
+Array [
+  Object {
+    "cmd": "npm i -g -C ~/.npm/lerna@2.0.0 lerna@2.0.0 && yarn install --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879 && ~/.npm/lerna@2.0.0/bin/lerna bootstrap --no-ci -- --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": Object {},
+    },
+  },
+]
+`;
+
+exports[`generateLockFiles() performs full npm install 1`] = `
+Array [
+  Object {
+    "cmd": "npm install --ignore-scripts  --no-audit && lerna bootstrap --no-ci -- --ignore-scripts  --no-audit",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": Object {},
+    },
+  },
+]
+`;
diff --git a/test/workers/branch/lock-files/__snapshots__/npm.spec.ts.snap b/test/workers/branch/lock-files/__snapshots__/npm.spec.ts.snap
new file mode 100644
index 0000000000000000000000000000000000000000..b85c9f10518e985e64ba3a5ddc97892b17499c53
--- /dev/null
+++ b/test/workers/branch/lock-files/__snapshots__/npm.spec.ts.snap
@@ -0,0 +1,41 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`generateLockFile catches errors 1`] = `Array []`;
+
+exports[`generateLockFile finds npm embedded in renovate 1`] = `Array []`;
+
+exports[`generateLockFile finds npm globally 1`] = `Array []`;
+
+exports[`generateLockFile generates lock files 1`] = `
+Array [
+  Object {
+    "cmd": "node node_modules/npm/bin/npm-cli.js dedupe",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": Object {},
+    },
+  },
+]
+`;
+
+exports[`generateLockFile performs full install 1`] = `Array []`;
+
+exports[`generateLockFile performs lock file updates 1`] = `
+Array [
+  Object {
+    "cmd": "node node_modules/npm/bin/npm-cli.js install --package-lock-only --no-audit some-dep@1.0.1",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": Object {},
+    },
+  },
+]
+`;
+
+exports[`generateLockFile performs npm-shrinkwrap.json updates (no package-lock.json) 1`] = `Array []`;
+
+exports[`generateLockFile performs npm-shrinkwrap.json updates 1`] = `Array []`;
+
+exports[`generateLockFile uses fallback npm 1`] = `Array []`;
diff --git a/test/workers/branch/lock-files/__snapshots__/pnpm.spec.ts.snap b/test/workers/branch/lock-files/__snapshots__/pnpm.spec.ts.snap
new file mode 100644
index 0000000000000000000000000000000000000000..00f4127041c57814bc967ab3603423d7e8760afa
--- /dev/null
+++ b/test/workers/branch/lock-files/__snapshots__/pnpm.spec.ts.snap
@@ -0,0 +1,79 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`generateLockFile catches errors 1`] = `
+Array [
+  Object {
+    "cmd": "node node_modules/pnpm/lib/bin/pnpm.js install --lockfile-only --ignore-scripts --ignore-pnpmfile",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": null,
+    },
+  },
+]
+`;
+
+exports[`generateLockFile finds pnpm embedded in renovate 1`] = `
+Array [
+  Object {
+    "cmd": "node /node_modules/renovate/node_modules/pnpm/lib/bin/pnpm.js install --lockfile-only --ignore-scripts --ignore-pnpmfile",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": null,
+    },
+  },
+]
+`;
+
+exports[`generateLockFile finds pnpm globally 1`] = `
+Array [
+  Object {
+    "cmd": "node /node_modules/pnpm/lib/bin/pnpm.js install --lockfile-only --ignore-scripts --ignore-pnpmfile",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": null,
+    },
+  },
+]
+`;
+
+exports[`generateLockFile generates lock files 1`] = `
+Array [
+  Object {
+    "cmd": "node node_modules/pnpm/lib/bin/pnpm.js install --lockfile-only --ignore-scripts --ignore-pnpmfile",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": null,
+    },
+  },
+]
+`;
+
+exports[`generateLockFile uses docker pnpm 1`] = `
+Array [
+  Object {
+    "cmd": "docker run --rm -v \\"some-dir\\":\\"some-dir\\" -v \\"some-cache-dir\\":\\"some-cache-dir\\" -e NPM_CONFIG_CACHE -e npm_config_store -w \\"some-dir\\" renovate/pnpm pnpm install --lockfile-only --ignore-scripts --ignore-pnpmfile",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": null,
+    },
+  },
+]
+`;
+
+exports[`generateLockFile uses fallback pnpm 1`] = `
+Array [
+  Object {
+    "cmd": "pnpm install --lockfile-only --ignore-scripts --ignore-pnpmfile",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": null,
+    },
+  },
+]
+`;
diff --git a/test/workers/branch/lock-files/__snapshots__/yarn.spec.ts.snap b/test/workers/branch/lock-files/__snapshots__/yarn.spec.ts.snap
new file mode 100644
index 0000000000000000000000000000000000000000..ab92f675a38d15bcc46e5706a4f61ac08107350d
--- /dev/null
+++ b/test/workers/branch/lock-files/__snapshots__/yarn.spec.ts.snap
@@ -0,0 +1,124 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`generateLockFile catches errors 1`] = `
+Array [
+  Object {
+    "cmd": "<yarn> install --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": null,
+    },
+  },
+]
+`;
+
+exports[`generateLockFile detects yarnIntegrity 1`] = `
+Array [
+  Object {
+    "cmd": "<yarn> install --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": Object {},
+    },
+  },
+  Object {
+    "cmd": "<yarn> upgrade some-dep --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": Object {},
+    },
+  },
+]
+`;
+
+exports[`generateLockFile finds yarn embedded in renovate 1`] = `
+Array [
+  Object {
+    "cmd": "<yarn> install --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": null,
+    },
+  },
+]
+`;
+
+exports[`generateLockFile finds yarn globally 1`] = `
+Array [
+  Object {
+    "cmd": "<yarn> install --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": null,
+    },
+  },
+]
+`;
+
+exports[`generateLockFile generates lock files 1`] = `
+Array [
+  Object {
+    "cmd": "<yarn> install --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": Object {},
+    },
+  },
+  Object {
+    "cmd": "npx yarn-deduplicate@1.1.1 --strategy fewer && yarn",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": Object {},
+    },
+  },
+  Object {
+    "cmd": "npx yarn-deduplicate@1.1.1 --strategy highest && yarn",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": Object {},
+    },
+  },
+]
+`;
+
+exports[`generateLockFile performs lock file updates 1`] = `
+Array [
+  Object {
+    "cmd": "<yarn> install --ignore-scripts --ignore-engines --ignore-platform --mutex file:/tmp/yarn.mutext",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": Object {},
+    },
+  },
+  Object {
+    "cmd": "<yarn> upgrade some-dep --ignore-scripts --ignore-engines --ignore-platform --mutex file:/tmp/yarn.mutext",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": Object {},
+    },
+  },
+]
+`;
+
+exports[`generateLockFile uses fallback yarn 1`] = `
+Array [
+  Object {
+    "cmd": "yarn install --ignore-scripts --ignore-engines --ignore-platform --mutex network:31879",
+    "options": Object {
+      "cwd": "some-dir",
+      "encoding": "utf-8",
+      "env": null,
+    },
+  },
+]
+`;
diff --git a/test/workers/branch/lock-files/lerna.spec.ts b/test/workers/branch/lock-files/lerna.spec.ts
index 4efabd08485f5e721cdb6ee145d6360df8540394..806ed0f74489fad3c610d40fa5200e99c1e179f3 100644
--- a/test/workers/branch/lock-files/lerna.spec.ts
+++ b/test/workers/branch/lock-files/lerna.spec.ts
@@ -1,15 +1,19 @@
-import * as _exec from '../../../../lib/util/exec';
+import { exec as _exec } from 'child_process';
 import * as _lernaHelper from '../../../../lib/manager/npm/post-update/lerna';
 import { platform as _platform } from '../../../../lib/platform';
 import { mocked } from '../../../util';
+import { mockExecAll } from '../../../execUtil';
 
-jest.mock('../../../../lib/util/exec');
+jest.mock('child_process');
 
-const exec = mocked(_exec).exec;
+const exec: jest.Mock<typeof _exec> = _exec as any;
 const lernaHelper = mocked(_lernaHelper);
 const platform = mocked(_platform);
 
 describe('generateLockFiles()', () => {
+  beforeEach(() => {
+    jest.resetAllMocks();
+  });
   it('returns if no lernaClient', async () => {
     const res = await lernaHelper.generateLockFiles(undefined, 'some-dir', {});
     expect(res.error).toBe(false);
@@ -18,7 +22,7 @@ describe('generateLockFiles()', () => {
     platform.getFile.mockResolvedValueOnce(
       JSON.stringify({ dependencies: { lerna: '2.0.0' } })
     );
-    exec.mockResolvedValueOnce({} as never);
+    const execSnapshots = mockExecAll(exec);
     const skipInstalls = true;
     const res = await lernaHelper.generateLockFiles(
       'npm',
@@ -27,12 +31,13 @@ describe('generateLockFiles()', () => {
       skipInstalls
     );
     expect(res.error).toBe(false);
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('performs full npm install', async () => {
     platform.getFile.mockResolvedValueOnce(
       JSON.stringify({ dependencies: { lerna: '2.0.0' } })
     );
-    exec.mockResolvedValueOnce({} as never);
+    const execSnapshots = mockExecAll(exec);
     const skipInstalls = false;
     const binarySource = 'global';
     const res = await lernaHelper.generateLockFiles(
@@ -43,19 +48,22 @@ describe('generateLockFiles()', () => {
       binarySource
     );
     expect(res.error).toBe(false);
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('generates yarn.lock files', async () => {
     platform.getFile.mockResolvedValueOnce(
       JSON.stringify({ devDependencies: { lerna: '2.0.0' } })
     );
-    exec.mockResolvedValueOnce({} as never);
+    const execSnapshots = mockExecAll(exec);
     const res = await lernaHelper.generateLockFiles('yarn', 'some-dir', {});
     expect(res.error).toBe(false);
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('defaults to latest', async () => {
     platform.getFile.mockReturnValueOnce(undefined);
-    exec.mockResolvedValueOnce({} as never);
+    const execSnapshots = mockExecAll(exec);
     const res = await lernaHelper.generateLockFiles('npm', 'some-dir', {});
     expect(res.error).toBe(false);
+    expect(execSnapshots).toMatchSnapshot();
   });
 });
diff --git a/test/workers/branch/lock-files/npm.spec.ts b/test/workers/branch/lock-files/npm.spec.ts
index 2cfcc7e81412dfd6464dc20fd6a65f6b76142950..d2867ec6f5dfb105b898c41622229d25308036bc 100644
--- a/test/workers/branch/lock-files/npm.spec.ts
+++ b/test/workers/branch/lock-files/npm.spec.ts
@@ -1,29 +1,26 @@
 import { getInstalledPath } from 'get-installed-path';
 import path from 'path';
 import _fs from 'fs-extra';
-import * as _exec from '../../../../lib/util/exec';
+import { exec as _exec } from 'child_process';
 import * as npmHelper from '../../../../lib/manager/npm/post-update/npm';
 import { mocked } from '../../../util';
+import { mockExecAll } from '../../../execUtil';
 
 jest.mock('fs-extra');
-jest.mock('../../../../lib/util/exec');
+jest.mock('child_process');
 jest.mock('get-installed-path');
 
 getInstalledPath.mockImplementation(() => null);
-const exec = mocked(_exec).exec;
+const exec: jest.Mock<typeof _exec> = _exec as any;
 const fs = mocked(_fs);
 
 describe('generateLockFile', () => {
+  beforeEach(() => {
+    jest.resetAllMocks();
+  });
   it('generates lock files', async () => {
     getInstalledPath.mockReturnValueOnce('node_modules/npm');
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
     const skipInstalls = true;
     const postUpdateOptions = ['npmDedupe'];
@@ -36,17 +33,11 @@ describe('generateLockFile', () => {
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.error).toBeUndefined();
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('performs lock file updates', async () => {
     getInstalledPath.mockReturnValueOnce('node_modules/npm');
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
     const skipInstalls = true;
     const updates = [
@@ -62,17 +53,11 @@ describe('generateLockFile', () => {
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.error).toBeUndefined();
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('performs npm-shrinkwrap.json updates', async () => {
     getInstalledPath.mockReturnValueOnce('node_modules/npm');
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.pathExists.mockImplementationOnce(() => true);
     fs.move = jest.fn();
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
@@ -98,17 +83,11 @@ describe('generateLockFile', () => {
     );
     expect(res.error).toBeUndefined();
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('performs npm-shrinkwrap.json updates (no package-lock.json)', async () => {
     getInstalledPath.mockReturnValueOnce('node_modules/npm');
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.pathExists.mockImplementationOnce(() => false);
     fs.move = jest.fn();
     fs.readFile = jest.fn((_, _1) => 'package-lock-contents') as never;
@@ -130,13 +109,11 @@ describe('generateLockFile', () => {
     );
     expect(res.error).toBeUndefined();
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('performs full install', async () => {
     getInstalledPath.mockReturnValueOnce('node_modules/npm');
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
     const skipInstalls = false;
     const binarySource = 'global';
@@ -149,13 +126,11 @@ describe('generateLockFile', () => {
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.error).toBeUndefined();
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('catches errors', async () => {
     getInstalledPath.mockReturnValueOnce('node_modules/npm');
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: 'some-error',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile = jest.fn(() => {
       throw new Error('not found');
     }) as never;
@@ -167,6 +142,7 @@ describe('generateLockFile', () => {
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.error).toBe(true);
     expect(res.lockFile).not.toBeDefined();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('finds npm embedded in renovate', async () => {
     getInstalledPath.mockImplementationOnce(() => {
@@ -176,10 +152,7 @@ describe('generateLockFile', () => {
     getInstalledPath.mockImplementationOnce(
       () => '/node_modules/renovate/node_modules/npm'
     );
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
     const res = await npmHelper.generateLockFile(
       'some-dir',
@@ -188,6 +161,7 @@ describe('generateLockFile', () => {
     );
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('finds npm globally', async () => {
     getInstalledPath.mockImplementationOnce(() => {
@@ -198,10 +172,7 @@ describe('generateLockFile', () => {
       throw new Error('not found');
     });
     getInstalledPath.mockImplementationOnce(() => '/node_modules/npm');
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
     const res = await npmHelper.generateLockFile(
       'some-dir',
@@ -210,6 +181,7 @@ describe('generateLockFile', () => {
     );
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('uses fallback npm', async () => {
     getInstalledPath.mockImplementationOnce(() => {
@@ -222,10 +194,7 @@ describe('generateLockFile', () => {
     getInstalledPath.mockImplementationOnce(() => {
       throw new Error('not found');
     });
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
     const res = await npmHelper.generateLockFile(
       'some-dir',
@@ -234,5 +203,6 @@ describe('generateLockFile', () => {
     );
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(execSnapshots).toMatchSnapshot();
   });
 });
diff --git a/test/workers/branch/lock-files/pnpm.spec.ts b/test/workers/branch/lock-files/pnpm.spec.ts
index 755771c749e6ad6ff893e48f14c7e24541f7073f..2aeb9cc305ba907c116af212d524998125e6ffe6 100644
--- a/test/workers/branch/lock-files/pnpm.spec.ts
+++ b/test/workers/branch/lock-files/pnpm.spec.ts
@@ -1,54 +1,62 @@
 import { getInstalledPath } from 'get-installed-path';
 import _fs from 'fs-extra';
-import * as _exec from '../../../../lib/util/exec';
+import { exec as _exec } from 'child_process';
 import { mocked } from '../../../util';
 import * as _pnpmHelper from '../../../../lib/manager/npm/post-update/pnpm';
+import { mockExecAll } from '../../../execUtil';
 
 jest.mock('fs-extra');
-jest.mock('../../../../lib/util/exec');
+jest.mock('child_process');
 jest.mock('get-installed-path');
 
 getInstalledPath.mockImplementation(() => null);
 
-const exec = mocked(_exec).exec;
+const exec: jest.Mock<typeof _exec> = _exec as any;
 const fs = mocked(_fs);
 const pnpmHelper = mocked(_pnpmHelper);
 
+let processEnv;
+
 describe('generateLockFile', () => {
   let env;
   let config;
   beforeEach(() => {
     config = { cacheDir: 'some-cache-dir' };
+
+    processEnv = process.env;
+    process.env = {
+      HTTP_PROXY: 'http://example.com',
+      HTTPS_PROXY: 'https://example.com',
+      NO_PROXY: 'localhost',
+      HOME: '/home/user',
+      PATH: '/tmp/path',
+    };
+  });
+  afterEach(() => {
+    process.env = processEnv;
   });
   it('generates lock files', async () => {
     getInstalledPath.mockReturnValueOnce('node_modules/pnpm');
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
     const res = await pnpmHelper.generateLockFile('some-dir', env, config);
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('uses docker pnpm', async () => {
     getInstalledPath.mockReturnValueOnce('node_modules/pnpm');
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
     config.binarySource = 'docker';
     const res = await pnpmHelper.generateLockFile('some-dir', env, config);
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('catches errors', async () => {
     getInstalledPath.mockReturnValueOnce('node_modules/pnpm');
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: 'some-error',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile = jest.fn(() => {
       throw new Error('not found');
     }) as never;
@@ -56,6 +64,7 @@ describe('generateLockFile', () => {
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.error).toBe(true);
     expect(res.lockFile).not.toBeDefined();
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('finds pnpm embedded in renovate', async () => {
     getInstalledPath.mockImplementationOnce(() => {
@@ -65,14 +74,12 @@ describe('generateLockFile', () => {
     getInstalledPath.mockImplementationOnce(
       () => '/node_modules/renovate/node_modules/pnpm'
     );
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
     const res = await pnpmHelper.generateLockFile('some-dir', env, config);
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('finds pnpm globally', async () => {
     getInstalledPath.mockImplementationOnce(() => {
@@ -83,14 +90,12 @@ describe('generateLockFile', () => {
       throw new Error('not found');
     });
     getInstalledPath.mockImplementationOnce(() => '/node_modules/pnpm');
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
     const res = await pnpmHelper.generateLockFile('some-dir', env, config);
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(execSnapshots).toMatchSnapshot();
   });
   it('uses fallback pnpm', async () => {
     getInstalledPath.mockImplementationOnce(() => {
@@ -103,14 +108,12 @@ describe('generateLockFile', () => {
     getInstalledPath.mockImplementationOnce(() => {
       throw new Error('not found');
     });
-    exec.mockResolvedValueOnce({
-      stdout: '',
-      stderr: '',
-    });
+    const execSnapshots = mockExecAll(exec);
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
     config.binarySource = 'global';
     const res = await pnpmHelper.generateLockFile('some-dir', env, config);
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(execSnapshots).toMatchSnapshot();
   });
 });
diff --git a/test/workers/branch/lock-files/yarn.spec.ts b/test/workers/branch/lock-files/yarn.spec.ts
index 8000617f5585c3d5d64568ab2cdfa615a7fb3d90..5e4c9cc3bb587e26a4f9840052c12de07d828824 100644
--- a/test/workers/branch/lock-files/yarn.spec.ts
+++ b/test/workers/branch/lock-files/yarn.spec.ts
@@ -1,29 +1,48 @@
 import { getInstalledPath } from 'get-installed-path';
 import _fs from 'fs-extra';
-import * as _exec from '../../../../lib/util/exec';
+import { exec as _exec } from 'child_process';
 import * as _yarnHelper from '../../../../lib/manager/npm/post-update/yarn';
 import { mocked } from '../../../util';
+import { ExecSnapshots, mockExecAll } from '../../../execUtil';
 
 jest.mock('fs-extra');
-jest.mock('../../../../lib/util/exec');
+jest.mock('child_process');
 jest.mock('get-installed-path');
 
 getInstalledPath.mockImplementation(() => null);
 
-const exec = mocked(_exec).exec;
+const exec: jest.Mock<typeof _exec> = _exec as any;
 const fs = mocked(_fs);
 const yarnHelper = mocked(_yarnHelper);
 
+let processEnv;
+
+// TODO: figure out snapshot similarity for each CI platform
+const fixSnapshots = (snapshots: ExecSnapshots): ExecSnapshots =>
+  snapshots.map(snapshot => ({
+    ...snapshot,
+    cmd: snapshot.cmd.replace(/^.*\/yarn.*?\.js\s+/, '<yarn> '),
+  }));
+
 describe('generateLockFile', () => {
   beforeEach(() => {
     delete process.env.YARN_MUTEX_FILE;
     jest.resetAllMocks();
-    exec.mockResolvedValue({
-      stdout: '',
-      stderr: '',
-    });
+
+    processEnv = process.env;
+    process.env = {
+      HTTP_PROXY: 'http://example.com',
+      HTTPS_PROXY: 'https://example.com',
+      NO_PROXY: 'localhost',
+      HOME: '/home/user',
+      PATH: '/tmp/path',
+    };
+  });
+  afterEach(() => {
+    process.env = processEnv;
   });
   it('generates lock files', async () => {
+    const execSnapshots = mockExecAll(exec);
     getInstalledPath.mockReturnValueOnce('node_modules/yarn');
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
     /** @type {NodeJS.ProcessEnv} */
@@ -34,8 +53,11 @@ describe('generateLockFile', () => {
     const res = await yarnHelper.generateLockFile('some-dir', env, config);
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(fixSnapshots(execSnapshots)).toMatchSnapshot();
   });
   it('performs lock file updates', async () => {
+    const execSnapshots = mockExecAll(exec);
+
     getInstalledPath.mockReturnValueOnce('node_modules/yarn');
 
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
@@ -44,8 +66,11 @@ describe('generateLockFile', () => {
       { depName: 'some-dep', isLockfileUpdate: true },
     ]);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(fixSnapshots(execSnapshots)).toMatchSnapshot();
   });
   it('detects yarnIntegrity', async () => {
+    const execSnapshots = mockExecAll(exec);
+
     getInstalledPath.mockReturnValueOnce('node_modules/yarn');
     fs.readFile = jest.fn(() => 'package-lock-contents') as never;
     const config = {
@@ -55,10 +80,11 @@ describe('generateLockFile', () => {
       { depName: 'some-dep', isLockfileUpdate: true },
     ]);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(fixSnapshots(execSnapshots)).toMatchSnapshot();
   });
   it('catches errors', async () => {
     getInstalledPath.mockReturnValueOnce('node_modules/yarn');
-    exec.mockResolvedValueOnce({
+    const execSnapshots = mockExecAll(exec, {
       stdout: '',
       stderr: 'some-error',
     });
@@ -69,8 +95,10 @@ describe('generateLockFile', () => {
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.error).toBe(true);
     expect(res.lockFile).not.toBeDefined();
+    expect(fixSnapshots(execSnapshots)).toMatchSnapshot();
   });
   it('finds yarn embedded in renovate', async () => {
+    const execSnapshots = mockExecAll(exec);
     getInstalledPath.mockImplementationOnce(() => {
       throw new Error('not found');
     });
@@ -82,8 +110,10 @@ describe('generateLockFile', () => {
     const res = await yarnHelper.generateLockFile('some-dir');
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(fixSnapshots(execSnapshots)).toMatchSnapshot();
   });
   it('finds yarn globally', async () => {
+    const execSnapshots = mockExecAll(exec);
     getInstalledPath.mockImplementationOnce(() => {
       throw new Error('not found');
     });
@@ -96,8 +126,10 @@ describe('generateLockFile', () => {
     const res = await yarnHelper.generateLockFile('some-dir');
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(fixSnapshots(execSnapshots)).toMatchSnapshot();
   });
   it('uses fallback yarn', async () => {
+    const execSnapshots = mockExecAll(exec);
     getInstalledPath.mockImplementationOnce(() => {
       throw new Error('not found');
     });
@@ -114,5 +146,6 @@ describe('generateLockFile', () => {
     });
     expect(fs.readFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toEqual('package-lock-contents');
+    expect(fixSnapshots(execSnapshots)).toMatchSnapshot();
   });
 });