Jahed Ahmed

macOS's Weird Temporary Directory

On a project, you might want to create a bunch of temporary directories to run tests against. It's a great way to isolate test cases that share the same fixture. Some of these tests might assert on a file path within these directories.

1
2
3
4
5
6
7
8
9
const testDirPath = path.resolve(os.tmpdir(), "test-");
const testDir = await fs.mkdtemp(testDirPath);

await copy(fixtureDir, testDir);

const result = doSomething(testDir);

const expected = path.resolve(testDir, "expected-file.txt");
expect(result).toEqual(expected);

That's all fine on Windows and Linux.

Note: For brevity, I've shortened some of the randomly generated characters.

1
2
result = "/tmp/test-dv2s/expected-file.txt";
expected = "/tmp/test-dv2s/expected-file.txt";

But on macOS, something weird happens.

1
2
result = "/private/var/folders/a2/cxod_f8ou3/T/test-dv2s/expected-file.txt";
expected = "/var/folders/a2/cxod_f8ou3/T/test-dv2s/expected-file.txt";

What's up with the /private? To resolve these sorts of path mismatches, having realpath in your terminal helps. It's a pretty common tool on Linux. macOS doesn't ship with it but it's easy to install via Homebrew.

1
brew install coreutils

Now let's check our expected path.

1
2
$ realpath /var/folders/a2/cxod_f8ou3/T/test-dv2s/expected-file.txt
/private/var/folders/a2/cxod_f8ou3/T/test-dv2s/expected-file.txt

So macOS seems to do some stuff to isolate temporary files. Probably a security thing. Now that we know that, we should resolve any paths within temporary directories to real paths.

1
2
3
const testDirPath = path.resolve(os.tmpdir(), "test-");
const testDirTemp = await fs.mkdtemp(testDirPath);
const testDir = await fs.realpath(testDirTemp); // new step

And it works!

1
2
result = "/private/var/folders/a2/cxod_f8ou3/T/test-dv2s/expected-file.txt";
expected = "/private/var/folders/a2/cxod_f8ou3/T/test-dv2s/expected-file.txt";

Let's simplify this setup so that we don't have to worry about what's real and what isn't.

1
2
3
4
5
const createTempDir = async (dirName: string): Promise<string> => {
return fs.realpath(
await fs.mkdtemp(path.resolve(os.tmpdir(), `${dirName}-`))
);
};

Now we can call a single function.

1
const testDir = await createTempDir("test");

Thanks for reading.

Related Tags