sapling/eden/integration/basic_test.py
Wez Furlong 4235784907 add .eden "magic" dir
Summary:
It's not really magic because we don't have a virtual directory
inode base any more.  Instead, we mkdir and populate it at mount time.

What is slightly magical about it is that we give it some special powers:

* We know the inode number of the eden dir and prevent unlink operations
  on it or inside it.
* The .eden dir is present in the contents of the root inode and will
  show up when that directory is `readdir`'d
* When resolving a child of a TreeInode by name, we know to return the
  magic `.eden` inode number.  This means that it is possible to `stat`
  and consume the `.eden` directory from any directory inside the eden
  mount, even though it won't show up in `readdir` for those child dirs.

The contents of the `.eden` dir are:

* `socket` - a symlink back to the unix domain socket that our thrift
  server is listening on.  This means that it is a simple
  `readlink(".eden/socket")` operation to discover both whether a directory
  is part of an eden mount and how to talk to the server.

* `root` - a symlink back to the root of this eden mount.  This allows
  using `readlink(".eden/root")` as a simple 1-step operation to find
  the root of an eden mount, and avoids needing to walk up directory
  by directory as is the common pattern for locating `.hg` or `.git`
  dirs.

Reviewed By: simpkins

Differential Revision: D4637285

fbshipit-source-id: 0eabf98b29144acccef5c83bd367493399dc55bb
2017-03-24 23:07:42 -07:00

241 lines
9.0 KiB
Python

#!/usr/bin/env python3
#
# Copyright (c) 2016-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
import errno
import os
import stat
import subprocess
from .lib import testcase
@testcase.eden_repo_test
class BasicTest:
'''Exercise some fundamental properties of the filesystem.
Listing directories, checking stat information, asserting
that the filesystem is reporting the basic information
about the sample git repo and that it is correct are all
things that are appropriate to include in this test case.
'''
def populate_repo(self):
self.repo.write_file('hello', 'hola\n')
self.repo.write_file('adir/file', 'foo!\n')
self.repo.write_file('bdir/test.sh', '#!/bin/bash\necho test\n',
mode=0o755)
self.repo.write_file('bdir/noexec.sh', '#!/bin/bash\necho test\n')
self.repo.symlink('slink', 'hello')
self.repo.commit('Initial commit.')
def test_fileList(self):
entries = sorted(os.listdir(self.mount))
self.assertEqual(['.eden', 'adir', 'bdir', 'hello', 'slink'], entries)
adir = os.path.join(self.mount, 'adir')
st = os.lstat(adir)
self.assertTrue(stat.S_ISDIR(st.st_mode))
self.assertEqual(st.st_uid, os.getuid())
self.assertEqual(st.st_gid, os.getgid())
hello = os.path.join(self.mount, 'hello')
st = os.lstat(hello)
self.assertTrue(stat.S_ISREG(st.st_mode))
slink = os.path.join(self.mount, 'slink')
st = os.lstat(slink)
self.assertTrue(stat.S_ISLNK(st.st_mode))
def test_symlinks(self):
slink = os.path.join(self.mount, 'slink')
self.assertEqual(os.readlink(slink), 'hello')
def test_regular(self):
hello = os.path.join(self.mount, 'hello')
with open(hello, 'r') as f:
self.assertEqual('hola\n', f.read())
def test_dir(self):
entries = sorted(os.listdir(os.path.join(self.mount, 'adir')))
self.assertEqual(['file'], entries)
filename = os.path.join(self.mount, 'adir', 'file')
with open(filename, 'r') as f:
self.assertEqual('foo!\n', f.read())
def test_create(self):
filename = os.path.join(self.mount, 'notinrepo')
with open(filename, 'w') as f:
f.write('created\n')
entries = sorted(os.listdir(self.mount))
self.assertEqual(['.eden', 'adir', 'bdir', 'hello', 'notinrepo', 'slink'],
entries)
with open(filename, 'r') as f:
self.assertEqual(f.read(), 'created\n')
st = os.lstat(filename)
self.assertEqual(st.st_size, 8)
self.assertTrue(stat.S_ISREG(st.st_mode))
def test_overwrite(self):
hello = os.path.join(self.mount, 'hello')
with open(hello, 'w') as f:
f.write('replaced\n')
st = os.lstat(hello)
self.assertEqual(st.st_size, len('replaced\n'))
def test_append(self):
hello = os.path.join(self.mount, 'bdir/test.sh')
with open(hello, 'a') as f:
f.write('echo more commands\n')
expected_data = '#!/bin/bash\necho test\necho more commands\n'
st = os.lstat(hello)
with open(hello, 'r') as f:
read_back = f.read()
self.assertEqual(expected_data, read_back)
self.assertEqual(len(expected_data), st.st_size)
def test_materialize(self):
hello = os.path.join(self.mount, 'hello')
# Opening for write should materialize the file with the same
# contents that we expect
with open(hello, 'r+') as f:
self.assertEqual('hola\n', f.read())
st = os.lstat(hello)
self.assertEqual(st.st_size, len('hola\n'))
def test_mkdir(self):
# Can't create a directory inside a file that is in the store
with self.assertRaises(OSError) as context:
os.mkdir(os.path.join(self.mount, 'hello', 'world'))
self.assertEqual(context.exception.errno, errno.ENOTDIR)
# Can't create a directory when a file of that name already exists
with self.assertRaises(OSError) as context:
os.mkdir(os.path.join(self.mount, 'hello'))
self.assertEqual(context.exception.errno, errno.EEXIST)
# Can't create a directory when a directory of that name already exists
with self.assertRaises(OSError) as context:
os.mkdir(os.path.join(self.mount, 'adir'))
self.assertEqual(context.exception.errno, errno.EEXIST)
buckout = os.path.join(self.mount, 'buck-out')
os.mkdir(buckout)
st = os.lstat(buckout)
self.assertTrue(stat.S_ISDIR(st.st_mode))
entries = sorted(os.listdir(self.mount))
self.assertEqual(['.eden', 'adir', 'bdir', 'buck-out', 'hello', 'slink'],
entries)
# Prove that we can recursively build out a directory tree
deep_name = os.path.join(buckout, 'foo', 'bar', 'baz')
os.makedirs(deep_name)
st = os.lstat(deep_name)
self.assertTrue(stat.S_ISDIR(st.st_mode))
# And that we can create a file in there too
deep_file = os.path.join(deep_name, 'file')
with open(deep_file, 'w') as f:
f.write('w00t')
st = os.lstat(deep_file)
self.assertTrue(stat.S_ISREG(st.st_mode))
def test_access(self):
def check_access(path, mode):
return os.access(os.path.join(self.mount, path), mode)
self.assertTrue(check_access('hello', os.R_OK))
self.assertTrue(check_access('hello', os.W_OK))
self.assertFalse(check_access('hello', os.X_OK))
self.assertTrue(check_access('bdir/test.sh', os.R_OK))
self.assertTrue(check_access('bdir/test.sh', os.W_OK))
self.assertTrue(check_access('bdir/test.sh', os.X_OK))
self.assertTrue(check_access('bdir/noexec.sh', os.R_OK))
self.assertTrue(check_access('bdir/noexec.sh', os.W_OK))
self.assertFalse(check_access('bdir/noexec.sh', os.X_OK))
cmd = [os.path.join(self.mount, 'bdir/test.sh')]
out = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
self.assertEqual(out, b'test\n')
cmd = [os.path.join(self.mount, 'bdir/noexec.sh')]
with self.assertRaises(OSError) as context:
out = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
self.assertEqual(errno.EACCES, context.exception.errno,
msg='attempting to run noexec.sh should fail with '
'EACCES')
def test_unmount(self):
entries = sorted(os.listdir(self.mount))
self.assertEqual(['.eden', 'adir', 'bdir', 'hello', 'slink'], entries)
self.assertTrue(self.eden.in_proc_mounts(self.mount))
self.eden.unmount(self.mount)
self.assertFalse(self.eden.in_proc_mounts(self.mount))
self.assertFalse(os.path.exists(self.mount))
self.eden.clone(self.repo_name, self.mount)
entries = sorted(os.listdir(self.mount))
self.assertEqual(['.eden', 'adir', 'bdir', 'hello', 'slink'], entries)
self.assertTrue(self.eden.in_proc_mounts(self.mount))
def test_unmount_remount(self):
# write a file into the overlay to test that it is still visible
# when we remount.
filename = os.path.join(self.mount, 'overlayonly')
with open(filename, 'w') as f:
f.write('foo!\n')
entries = sorted(os.listdir(self.mount))
self.assertEqual(['.eden', 'adir', 'bdir', 'hello', 'overlayonly', 'slink'],
entries)
self.assertTrue(self.eden.in_proc_mounts(self.mount))
# Unmount the client with --no-forget
self.eden.run_cmd('unmount', '-n', self.mount)
self.assertFalse(self.eden.in_proc_mounts(self.mount))
entries = sorted(os.listdir(self.mount))
self.assertEqual([], entries)
# Now remount it with the mount command
self.eden.run_cmd('mount', self.mount)
self.assertTrue(self.eden.in_proc_mounts(self.mount))
entries = sorted(os.listdir(self.mount))
self.assertEqual(['.eden', 'adir', 'bdir', 'hello', 'overlayonly', 'slink'],
entries)
with open(filename, 'r') as f:
self.assertEqual('foo!\n', f.read(), msg='overlay file is correct')
def test_double_unmount(self):
# Test calling "unmount -n" twice. The second should fail, but edenfs
# should still work normally afterwards
self.eden.run_cmd('unmount', '-n', self.mount)
self.eden.run_unchecked('unmount', '-n', self.mount)
# Now remount it with the mount command
self.eden.run_cmd('mount', self.mount)
self.assertTrue(self.eden.in_proc_mounts(self.mount))
entries = sorted(os.listdir(self.mount))
self.assertEqual(['.eden', 'adir', 'bdir', 'hello', 'slink'], entries)