diff --git a/lib/manager/poetry/artifacts.ts b/lib/manager/poetry/artifacts.ts
index e935f71194daf5bc234b75dbfedbba2f1b6fc28a..7e46960ccba077d53c1cfed62ea7301eed2b4dc3 100644
--- a/lib/manager/poetry/artifacts.ts
+++ b/lib/manager/poetry/artifacts.ts
@@ -1,49 +1,51 @@
 import is from '@sindresorhus/is';
-import { parse, join } from 'upath';
-import { outputFile, readFile } from 'fs-extra';
 import { exec, ExecOptions } from '../../util/exec';
 import { logger } from '../../logger';
 import { UpdateArtifact, UpdateArtifactsResult } from '../common';
-import { platform } from '../../platform';
+import {
+  getSiblingFileName,
+  getSubDirectory,
+  readLocalFile,
+  writeLocalFile,
+} from '../../util/fs';
 
 export async function updateArtifacts({
   packageFileName,
   updatedDeps,
   newPackageFileContent,
-  config,
 }: UpdateArtifact): Promise<UpdateArtifactsResult[] | null> {
   logger.debug(`poetry.updateArtifacts(${packageFileName})`);
   if (!is.nonEmptyArray(updatedDeps)) {
     logger.debug('No updated poetry deps - returning null');
     return null;
   }
-  const subDirectory = parse(packageFileName).dir;
-  let lockFileName = join(subDirectory, 'poetry.lock');
-  let existingLockFileContent = await platform.getFile(lockFileName);
+  const subDirectory = getSubDirectory(packageFileName);
+  // Try poetry.lock first
+  let lockFileName = getSiblingFileName(packageFileName, 'poetry.lock');
+  let existingLockFileContent = await readLocalFile(lockFileName);
   if (!existingLockFileContent) {
-    lockFileName = join(subDirectory, 'pyproject.lock');
-    existingLockFileContent = await platform.getFile(lockFileName);
+    // Try pyproject.lock next
+    lockFileName = getSiblingFileName(packageFileName, 'pyproject.lock');
+    existingLockFileContent = await readLocalFile(lockFileName);
     if (!existingLockFileContent) {
       logger.debug(`No lock file found`);
       return null;
     }
   }
   logger.debug(`Updating ${lockFileName}`);
-  const localPackageFileName = join(config.localDir, packageFileName);
-  const localLockFileName = join(config.localDir, lockFileName);
   try {
-    await outputFile(localPackageFileName, newPackageFileContent);
+    await writeLocalFile(packageFileName, newPackageFileContent);
     const cmd: string[] = [];
     for (let i = 0; i < updatedDeps.length; i += 1) {
       const dep = updatedDeps[i];
       cmd.push(`poetry update --lock --no-interaction ${dep}`);
     }
     const execOptions: ExecOptions = {
-      cwd: join(config.localDir, subDirectory),
+      subDirectory,
       docker: { image: 'renovate/poetry' },
     };
     await exec(cmd, execOptions);
-    const newPoetryLockContent = await readFile(localLockFileName, 'utf8');
+    const newPoetryLockContent = await readLocalFile(lockFileName);
     if (existingLockFileContent === newPoetryLockContent) {
       logger.debug(`${lockFileName} is unchanged`);
       return null;
diff --git a/lib/util/exec/index.ts b/lib/util/exec/index.ts
index d47e37954ce956ef83a69386ae9c972215ec6ee0..e618acebe24ce00994612eb451d9ebb382ffb187 100644
--- a/lib/util/exec/index.ts
+++ b/lib/util/exec/index.ts
@@ -1,3 +1,4 @@
+import { join } from 'path';
 import { hrtime } from 'process';
 import { promisify } from 'util';
 import {
@@ -32,6 +33,7 @@ const pExec: (
 type ExtraEnv<T = unknown> = Record<string, T>;
 
 export interface ExecOptions extends ChildProcessExecOptions {
+  subDirectory?: string;
   extraEnv?: Opt<ExtraEnv>;
   docker?: Opt<DockerOptions>;
 }
@@ -77,13 +79,19 @@ export async function exec(
   cmd: string | string[],
   opts: ExecOptions = {}
 ): Promise<ExecResult> {
-  const { env, extraEnv, docker } = opts;
-  const cwd = opts.cwd || execConfig.localDir;
+  const { env, extraEnv, docker, subDirectory } = opts;
+  let cwd;
+  // istanbul ignore if
+  if (subDirectory) {
+    cwd = join(execConfig.localDir, subDirectory);
+  }
+  cwd = cwd || opts.cwd || execConfig.localDir;
   const childEnv = createChildEnv(env, extraEnv);
 
   const execOptions: ExecOptions = { ...opts };
   delete execOptions.extraEnv;
   delete execOptions.docker;
+  delete execOptions.subDirectory;
 
   const pExecOptions: ChildProcessExecOptions & { encoding: string } = {
     encoding: 'utf-8',
diff --git a/lib/util/fs.ts b/lib/util/fs.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c2975955053f900ce7d3d071ee6a296c1d28f970
--- /dev/null
+++ b/lib/util/fs.ts
@@ -0,0 +1,40 @@
+import { parse, join } from 'upath';
+import { outputFile, readFile } from 'fs-extra';
+import { logger } from '../logger';
+
+let localDir = '';
+
+export function setFsConfig(config: any): void {
+  localDir = config.localDir;
+}
+
+export function getSubDirectory(fileName: string): string {
+  return parse(fileName).dir;
+}
+
+export function getSiblingFileName(
+  existingFileNameWithPath: string,
+  otherFileName: string
+): string {
+  const subDirectory = getSubDirectory(existingFileNameWithPath);
+  return join(subDirectory, otherFileName);
+}
+
+export async function readLocalFile(fileName: string): Promise<string> {
+  const localFileName = join(localDir, fileName);
+  try {
+    const fileContent = await readFile(localFileName, 'utf8');
+    return fileContent;
+  } catch (err) /* istanbul ignore next */ {
+    logger.trace({ err }, 'Error reading local file');
+    return null;
+  }
+}
+
+export async function writeLocalFile(
+  fileName: string,
+  fileContent: string
+): Promise<void> {
+  const localFileName = join(localDir, fileName);
+  await outputFile(localFileName, fileContent);
+}
diff --git a/lib/util/index.ts b/lib/util/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..181fa5ee30c1403baf389a03a14ac8480b2878f2
--- /dev/null
+++ b/lib/util/index.ts
@@ -0,0 +1,7 @@
+import { setExecConfig } from './exec';
+import { setFsConfig } from './fs';
+
+export function setUtilConfig(config: any): void {
+  setExecConfig(config);
+  setFsConfig(config);
+}
diff --git a/lib/workers/global/index.ts b/lib/workers/global/index.ts
index 0762814b5e859b8bda071fd8568d1f4c00caee30..409b7e255a204fe3bc5445dccdd9c8f6d01371b4 100644
--- a/lib/workers/global/index.ts
+++ b/lib/workers/global/index.ts
@@ -12,7 +12,7 @@ import { initPlatform } from '../../platform';
 import * as hostRules from '../../util/host-rules';
 import { printStats } from '../../util/got/stats';
 import * as limits from './limits';
-import { setExecConfig } from '../../util/exec';
+import { setUtilConfig } from '../../util';
 
 type RenovateConfig = configParser.RenovateConfig;
 type RenovateRepository = configParser.RenovateRepository;
@@ -77,7 +77,7 @@ export async function start(): Promise<0 | 1> {
         break;
       }
       const repoConfig = await getRepositoryConfig(config, repository);
-      setExecConfig(repoConfig);
+      setUtilConfig(repoConfig);
       if (repoConfig.hostRules) {
         hostRules.clear();
         repoConfig.hostRules.forEach(rule => hostRules.add(rule));
diff --git a/test/manager/poetry/artifacts.spec.ts b/test/manager/poetry/artifacts.spec.ts
index c894f631e9d3bbe0c6be47c643bbd89f2c528b05..bb6097ee639224fbb9d3300de88b24635f29b222 100644
--- a/test/manager/poetry/artifacts.spec.ts
+++ b/test/manager/poetry/artifacts.spec.ts
@@ -2,7 +2,6 @@ import { join } from 'upath';
 import _fs from 'fs-extra';
 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 { envMock, mockExecAll } from '../../execUtil';
 import * as _env from '../../../lib/util/exec/env';
@@ -16,7 +15,6 @@ jest.mock('../../../lib/util/exec/env');
 const fs: jest.Mocked<typeof _fs> = _fs as any;
 const exec: jest.Mock<typeof _exec> = _exec as any;
 const env = mocked(_env);
-const platform = mocked(_platform);
 
 const config = {
   localDir: join('/tmp/github/some/repo'),
@@ -50,7 +48,7 @@ describe('.updateArtifacts()', () => {
     ).toBeNull();
   });
   it('returns null if unchanged', async () => {
-    platform.getFile.mockResolvedValueOnce('Current poetry.lock');
+    fs.readFile.mockReturnValueOnce('Current poetry.lock' as any);
     const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('Current poetry.lock' as any);
     const updatedDeps = ['dep1'];
@@ -65,7 +63,7 @@ describe('.updateArtifacts()', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('returns updated poetry.lock', async () => {
-    platform.getFile.mockResolvedValueOnce('Old poetry.lock');
+    fs.readFile.mockResolvedValueOnce('Old poetry.lock' as any);
     const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('New poetry.lock' as any);
     const updatedDeps = ['dep1'];
@@ -85,7 +83,7 @@ describe('.updateArtifacts()', () => {
       binarySource: BinarySource.Docker,
       dockerUser: 'foobar',
     });
-    platform.getFile.mockResolvedValueOnce('Old poetry.lock');
+    fs.readFile.mockResolvedValueOnce('Old poetry.lock' as any);
     const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('New poetry.lock' as any);
     const updatedDeps = ['dep1'];
@@ -102,7 +100,7 @@ describe('.updateArtifacts()', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('catches errors', async () => {
-    platform.getFile.mockResolvedValueOnce('Current poetry.lock');
+    fs.readFile.mockResolvedValueOnce('Current poetry.lock' as any);
     fs.outputFile.mockImplementationOnce(() => {
       throw new Error('not found');
     });