def makegitrepo(tmpdir, name): path = os.path.join(tmpdir, name) def system(cmd): check_call(cmd, cwd=path) os.mkdir(path) system(GIT + ['init']) readme = os.path.join(path, 'README.md') contents(readme, "Hello\n") system(GIT + ['add', 'README.md']) system(GIT + ['commit', '-m', 'Added readme']) return path
def _addfake(name, createfile): # create a fake repo and add it tr = TempRepo(tmpdir, name) tf = os.path.join(HOME, createfile) contents(tr.remotepath + '/HOMELY.py', """ from homely.files import lineinfile lineinfile('~/%s', 'Hello from %s') """ % (createfile, name)) assert not os.path.exists(tf) system(HOMELY('add') + [tr.url]) assert contents(tf) == "Hello from %s\n" % name return tr
def _addfake(name, createfile): # create a fake repo and add it tr = TempRepo(tmpdir, name) tf = os.path.join(HOME, createfile) contents( tr.remotepath + '/HOMELY.py', """ from homely.files import lineinfile lineinfile('~/%s', 'Hello from %s') """ % (createfile, name)) assert not os.path.exists(tf) system(HOMELY('add') + [tr.url]) assert contents(tf) == "Hello from %s\n" % name return tr
def test_homely_remove(tmpdir, HOME): system = getsystemfn(HOME) def _addfake(name, createfile): # create a fake repo and add it tr = TempRepo(tmpdir, name) tf = os.path.join(HOME, createfile) contents( tr.remotepath + '/HOMELY.py', """ from homely.files import lineinfile lineinfile('~/%s', 'Hello from %s') """ % (createfile, name)) assert not os.path.exists(tf) system(HOMELY('add') + [tr.url]) assert contents(tf) == "Hello from %s\n" % name return tr r1 = _addfake('repo1', 'file1.txt') r2 = _addfake('repo2', 'file2.txt') r3 = _addfake('repo3', 'file3.txt') # check that all the repos are there checkrepolist(HOME, system, [r1, r2, r3]) assert contents(HOME + '/file1.txt', "Hello from repo1\n") assert contents(HOME + '/file2.txt', "Hello from repo2\n") assert contents(HOME + '/file3.txt', "Hello from repo3\n") # Check that the repo can be removed. system(HOMELY('forget') + [r1.repoid]) checkrepolist(HOME, system, [r2, r3]) assert contents(HOME + '/file1.txt', "Hello from repo1\n") assert contents(HOME + '/file2.txt', "Hello from repo2\n") assert contents(HOME + '/file3.txt', "Hello from repo3\n") # now run an update to make repo1's files go away system(HOMELY('update')) assert not os.path.exists(HOME + '/file1.txt') assert contents(HOME + '/file2.txt', "Hello from repo2\n") assert contents(HOME + '/file3.txt', "Hello from repo3\n") # Test removing multiple repos, but using local path this time # Note that because we don't use --update, the created files will still be # sitting around on disk system(HOMELY('forget') + ['~/repo2', '~/repo3']) checkrepolist(HOME, system, []) # repo2 and repo3 are stilling going to hang around on disk assert os.path.exists(HOME + '/repo2') assert os.path.exists(HOME + '/repo3') assert not os.path.exists(HOME + '/file1.txt') assert contents(HOME + '/file2.txt', "Hello from repo2\n") assert contents(HOME + '/file3.txt', "Hello from repo3\n")
def test_homely_remove(tmpdir, HOME): system = getsystemfn(HOME) def _addfake(name, createfile): # create a fake repo and add it tr = TempRepo(tmpdir, name) tf = os.path.join(HOME, createfile) contents(tr.remotepath + '/HOMELY.py', """ from homely.files import lineinfile lineinfile('~/%s', 'Hello from %s') """ % (createfile, name)) assert not os.path.exists(tf) system(HOMELY('add') + [tr.url]) assert contents(tf) == "Hello from %s\n" % name return tr r1 = _addfake('repo1', 'file1.txt') r2 = _addfake('repo2', 'file2.txt') r3 = _addfake('repo3', 'file3.txt') # check that all the repos are there checkrepolist(HOME, system, [r1, r2, r3]) assert contents(HOME + '/file1.txt', "Hello from repo1\n") assert contents(HOME + '/file2.txt', "Hello from repo2\n") assert contents(HOME + '/file3.txt', "Hello from repo3\n") # Check that the repo can be removed. system(HOMELY('forget') + [r1.repoid]) checkrepolist(HOME, system, [r2, r3]) assert contents(HOME + '/file1.txt', "Hello from repo1\n") assert contents(HOME + '/file2.txt', "Hello from repo2\n") assert contents(HOME + '/file3.txt', "Hello from repo3\n") # now run an update to make repo1's files go away system(HOMELY('update')) assert not os.path.exists(HOME + '/file1.txt') assert contents(HOME + '/file2.txt', "Hello from repo2\n") assert contents(HOME + '/file3.txt', "Hello from repo3\n") # Test removing multiple repos, but using local path this time # Note that because we don't use --update, the created files will still be # sitting around on disk system(HOMELY('forget') + ['~/repo2', '~/repo3']) checkrepolist(HOME, system, []) # repo2 and repo3 are stilling going to hang around on disk assert os.path.exists(HOME + '/repo2') assert os.path.exists(HOME + '/repo3') assert not os.path.exists(HOME + '/file1.txt') assert contents(HOME + '/file2.txt', "Hello from repo2\n") assert contents(HOME + '/file3.txt', "Hello from repo3\n")
def _addfake(name, createfile): # create a fake repo and add it tr = TempRepo(tmpdir, name) tf = os.path.join(tr.remotepath, createfile) with open(tf, 'w') as f: f.write('hello world') contents(tr.remotepath + '/HOMELY.py', """ from homely.files import symlink symlink('%s', '~/%s') """ % (createfile, createfile)) system(HOMELY('add') + [tr.url]) local = '%s/%s' % (tr.suggestedlocal(HOME), createfile) assert os.readlink('%s/%s' % (HOME, createfile)) == local
def _addfake(name, createfile): # create a fake repo and add it tr = TempRepo(tmpdir, name) tf = os.path.join(tr.remotepath, createfile) with open(tf, 'w') as f: f.write('hello world') contents( tr.remotepath + '/HOMELY.py', """ from homely.files import symlink symlink('%s', '~/%s') """ % (createfile, createfile)) system(HOMELY('add') + [tr.url]) local = '%s/%s' % (tr.suggestedlocal(HOME), createfile) assert os.readlink('%s/%s' % (HOME, createfile)) == local
def test_cleanup_everything(tmpdir): ''' test that recreating the engine, running nothing on it, then calling cleanup() will remove all of things that might be lying around ''' from homely._engine2 import Engine from homely.files import MakeDir, MakeSymlink, LineInFile cfgpath = gettmpfilepath(tmpdir, '.json') d1 = gettmpfilepath(tmpdir, '.d') d1f1 = os.path.join(d1, 'sub-file.txt') l1 = gettmpfilepath(tmpdir, '.lnk') e = Engine(cfgpath) e.run(MakeDir(d1)) e.run(MakeSymlink(d1, l1)) e.run(LineInFile(d1f1, "AAA")) e.cleanup(e.RAISE) del e assert os.path.isdir(d1) assert contents(d1f1, "AAA\n") assert os.path.islink(l1) and os.readlink(l1) == d1 # if we recreate the engine with nothing on it, it should clean everything # up e = Engine(cfgpath) e.cleanup(e.RAISE) assert not os.path.exists(d1) assert not os.path.islink(l1)
def test_lineinfile_knows_about_ownership(HOME, tmpdir): system = getsystemfn(HOME) # put the 'AAA' line into my file1.txt f1 = HOME + '/file1.txt' contents(f1, 'AAA\n') # create a fake repo and add it tr = TempRepo(tmpdir, 'dotfiles') contents(tr.remotepath + '/HOMELY.py', """ from homely.general import lineinfile lineinfile('file1.txt', 'AAA') lineinfile('file1.txt', 'BBB') """) system(HOMELY('add') + [tr.url]) # check that my file1.txt now has both lines assert contents(f1) == "AAA\nBBB\n" # now remove the repo and do a full update to trigger cleanup system(HOMELY('forget') + [tr.url]) system(HOMELY('update')) # run homely update a few more times to confuse it system(HOMELY('update')) system(HOMELY('update')) # check that only the 'BBB' line was removed assert contents(f1) == "AAA\n"
def test_lineinfile_knows_about_ownership(HOME, tmpdir): system = getsystemfn(HOME) # put the 'AAA' line into my file1.txt f1 = HOME + '/file1.txt' contents(f1, 'AAA\n') # create a fake repo and add it tr = TempRepo(tmpdir, 'dotfiles') contents( tr.remotepath + '/HOMELY.py', """ from homely.general import lineinfile lineinfile('file1.txt', 'AAA') lineinfile('file1.txt', 'BBB') """) system(HOMELY('add') + [tr.url]) # check that my file1.txt now has both lines assert contents(f1) == "AAA\nBBB\n" # now remove the repo and do a full update to trigger cleanup system(HOMELY('forget') + [tr.url]) system(HOMELY('update')) # run homely update a few more times to confuse it system(HOMELY('update')) system(HOMELY('update')) # check that only the 'BBB' line was removed assert contents(f1) == "AAA\n"
def test_add_greenfield_repos(tmpdir, HOME): """ Work or at least die with a helpful error message when ... """ from homely._errors import ERR_NO_COMMITS, ERR_NOT_A_REPO, ERR_NO_SCRIPT system = getsystemfn(HOME) def _repolist(): cmd = HOMELY('repolist') + ['--format', '%(localpath)s'] return list(filter(None, system(cmd).strip().split("\n"))) # Target dir is not a git repo notarepo = HOME + '/not_a_repo' os.mkdir(notarepo) contents(notarepo + '/HOMELY.py', "print('YES')\n") output = system(HOMELY('add') + [notarepo], expecterror=1) assert ERR_NOT_A_REPO in output del notarepo, output # this should have resulted in nothing being added to homely assert not len(_repolist()) # Target dir doesn't have any commits nocommits = HOME + '/nocommits' os.mkdir(nocommits) contents(nocommits + '/HOMELY.py', "print('YES')\n") system(['git', 'init'], cwd=nocommits) assert os.path.exists(nocommits + '/.git') output = system(HOMELY('add') + [nocommits], expecterror=1) assert ERR_NO_COMMITS in output del nocommits, output # this should have resulted in nothing being added to homely assert not len(_repolist()) # Target dir doesn't have a HOMELY.py script - when we add a repo that # doesn't have a HOMELY.py script we should raise an error and provide # instructions on how to write a HOMELY.py script (and not try to do the # homely update). noscript = HOME + '/noscript' os.mkdir(noscript) readme = noscript + '/README.md' contents(readme, "Hello world!\n") system(['git', 'init'], cwd=noscript) system(['git', 'config', 'user.name', "fred"], cwd=noscript) system(['git', 'config', 'user.email', "fred@example"], cwd=noscript) system(['git', 'add', 'README.md'], cwd=noscript) system(['git', 'commit', '-m', "Added readme"], cwd=noscript) output = system(HOMELY('add') + [noscript], expecterror=1) assert ERR_NO_SCRIPT in output # the repo should have added to homely assert [noscript] == _repolist()
def test_add_greenfield_repos(tmpdir, HOME): """ Work or at least die with a helpful error message when ... """ from homely._errors import ERR_NO_COMMITS, ERR_NOT_A_REPO, ERR_NO_SCRIPT system = getsystemfn(HOME) def _repolist(): cmd = HOMELY('repolist') + ['--format', '%(localpath)s'] return list(filter(None, system(cmd).strip().split("\n"))) # Target dir is not a git repo notarepo = HOME + '/not_a_repo' os.mkdir(notarepo) contents(notarepo + '/HOMELY.py', "print('YES')\n") output = system(HOMELY('add') + [notarepo], expecterror=1) assert ERR_NOT_A_REPO in output del notarepo, output # this should have resulted in nothing being added to homely assert not len(_repolist()) # Target dir doesn't have any commits nocommits = HOME + '/nocommits' os.mkdir(nocommits) contents(nocommits + '/HOMELY.py', "print('YES')\n") system(['git', 'init'], cwd=nocommits) assert os.path.exists(nocommits + '/.git') output = system(HOMELY('add') + [nocommits], expecterror=1) assert ERR_NO_COMMITS in output del nocommits, output # this should have resulted in nothing being added to homely assert not len(_repolist()) # Target dir doesn't have a HOMELY.py script - when we add a repo that # doesn't have a HOMELY.py script we should raise an error and provide # instructions on how to write a HOMELY.py script (and not try to do the # homely update). noscript = HOME + '/noscript' os.mkdir(noscript) readme = noscript + '/README.md' contents(readme, "Hello world!\n") system(['git', 'init'], cwd=noscript) system(['git', 'config', 'user.name', "fred"], cwd=noscript) system(['git', 'config', 'user.email', "fred@example"], cwd=noscript) system(['git', 'add', 'README.md'], cwd=noscript) system(['git', 'commit', '-m', "Added readme"], cwd=noscript) output = system(HOMELY('add') + [noscript], expecterror=1) assert ERR_NO_SCRIPT in output # the repo should have added to homely assert [noscript] == _repolist()
def test_lineinfile_usage(tmpdir): from homely._engine2 import Engine from homely.files import WHERE_END, WHERE_TOP, LineInFile cfgpath = gettmpfilepath(tmpdir, '.json') f1 = gettmpfilepath(tmpdir) # make sure the following types of input raise exceptions bad = [ "", # empty line "something\n", # a string containing a newline " \t", # a line containing nothing but whitespace ] for b in bad: try: LineInFile(f1, b) except Exception: # we got an exception pass else: raise Exception("LineInFile should not accept input %s" % repr(b)) # make sure LineInFile() doesn't override an existing line contents(f1, "AAA\nBBB\n") e = Engine(cfgpath) e.run(LineInFile(f1, "BBB")) assert contents(f1) == "AAA\nBBB\n" # make sure LineInFile() puts a new line at the end of the file by default e = Engine(cfgpath) e.run(LineInFile(f1, "CCC")) assert contents(f1) == "AAA\nBBB\nCCC\n" # make sure LineInFile() doesn't move a line unnecessarily e = Engine(cfgpath) e.run(LineInFile(f1, "BBB")) assert contents(f1) == "AAA\nBBB\nCCC\n" # make sure LineInFile() can move a line to the TOP of the file e = Engine(cfgpath) e.run(LineInFile(f1, "BBB", WHERE_TOP)) assert contents(f1) == "BBB\nAAA\nCCC\n" # make sure LineInFile() can move a line to the END of the file e = Engine(cfgpath) e.run(LineInFile(f1, "BBB", WHERE_END)) assert contents(f1) == "AAA\nCCC\nBBB\n" # make sure LineInFile() doesn't blow away empty lines # - adding to end of file contents(f1, "\n\n", strip=False) e = Engine(cfgpath) e.run(LineInFile(f1, "AAA")) assert contents(f1) == "\n\nAAA\n" # - adding to start of file contents(f1, "\n\n", strip=False) e = Engine(cfgpath) e.run(LineInFile(f1, "AAA", WHERE_TOP)) assert contents(f1) == "AAA\n\n\n" # - when restoring the file e = Engine(cfgpath) e.cleanup(e.RAISE) del e assert contents(f1) == "\n\n" # - replacing something in the middle contents(f1, "\n\nAAA\n\n\n", strip=False) e = Engine(cfgpath) e.run(LineInFile(f1, "AAA")) assert contents(f1) == "\n\nAAA\n\n\n" # make sure LineInFile() respects existing line endings # NOTE: in python2 we always use Universal Newlines when reading the file, # which tricks us into using "\n" when writing the file if sys.version_info[0] > 2: # - windows contents(f1, "AAA\r\nBBB\r\n") e = Engine(cfgpath) e.run(LineInFile(f1, "CCC")) assert contents(f1) == "AAA\r\nBBB\r\nCCC\r\n" # - mac contents(f1, "AAA\rBBB\r") e = Engine(cfgpath) e.run(LineInFile(f1, "BBB", WHERE_TOP)) assert contents(f1) == "BBB\rAAA\r" # make sure a file that starts empty is left empty after cleanup contents(f1, "") e = Engine(cfgpath) e.run(LineInFile(f1, "AAA")) assert contents(f1) == "AAA\n" e = Engine(cfgpath) e.cleanup(e.RAISE) assert contents(f1) == ""
def test_blockinfile_lineinfile_cleanup_interaction(tmpdir): ''' Test that when a LineInFile is cleaned up, any other LineInFile() or BlockInFile() that affected the same file, needs a chance to re-check whether it is still valid. If *any* of the remaining Helpers needs to be reapplied, then they *all* need to be reapplied. ''' from homely._engine2 import Engine from homely.files import LineInFile, BlockInFile cfgpath = gettmpfilepath(tmpdir, '.json') f1 = gettmpfilepath(tmpdir, '.txt') def bif(filename, lines): # shorthand constructor for BlockInFile() return BlockInFile(filename, lines, None, "PRE", "POST") # check that a LineInFile followed by a BlockInFile that both try to add # the same line will result in the file containing both things, even if # you rerun it many times for i in range(3): e = Engine(cfgpath) e.run(LineInFile(f1, "AAA")) e.run(bif(f1, ["AAA"])) e.cleanup(e.RAISE) del e assert contents(f1) == "AAA\nPRE\nAAA\nPOST\n" # check that a BlockInFile followed by a LineInFile that adds the same line # will result in the LineInFile not having any effect, even if you rerun it # many times os.unlink(f1) for i in range(3): e = Engine(cfgpath) e.run(bif(f1, ["AAA"])) e.run(LineInFile(f1, "AAA")) e.cleanup(e.RAISE) del e assert contents(f1) == "PRE\nAAA\nPOST\n" # check that removing the LineInFile doesn't destroy the contents added by # BlockInFile e = Engine(cfgpath) e.run(bif(f1, ["AAA"])) e.cleanup(e.RAISE) del e assert contents(f1) == "PRE\nAAA\nPOST\n" # put both things back in ... e = Engine(cfgpath) e.run(bif(f1, ["AAA"])) e.run(LineInFile(f1, "AAA")) e.cleanup(e.RAISE) del e assert contents(f1) == "PRE\nAAA\nPOST\n" # test that the LineInFile keeps itself there after removing the # BlockInFile e = Engine(cfgpath) e.run(LineInFile(f1, "AAA")) e.cleanup(e.RAISE) del e assert contents(f1) == "AAA\n"
def test_homely_update(HOME, tmpdir): system = getsystemfn(HOME) def _addfake(name, createfile): # create a fake repo and add it tr = TempRepo(tmpdir, name) tf = os.path.join(HOME, createfile) contents( tr.remotepath + '/HOMELY.py', """ from homely.files import lineinfile lineinfile('~/%s', 'Hello from %s') """ % (createfile, name)) assert not os.path.exists(tf) system(HOMELY('add') + [tr.url]) assert contents(tf) == "Hello from %s\n" % name return tr template = (""" from homely.files import lineinfile lineinfile(%r, %r) """) # add some fake repos r1 = _addfake('repo1', 'file1.txt') r2 = _addfake('repo2', 'file2.txt') r3 = _addfake('repo3', 'file3.txt') # update the repos slightly, run an update, ensure all changes have gone # through (including cleanup of old content) contents(r1.remotepath + '/HOMELY.py', template % ('~/file1.txt', 'AAA')) contents(r2.remotepath + '/HOMELY.py', template % ('~/file2.txt', 'AAA')) contents(r3.remotepath + '/HOMELY.py', template % ('~/file3.txt', 'AAA')) system(HOMELY('update')) assert contents(HOME + '/file1.txt') == "AAA\n" assert contents(HOME + '/file2.txt') == "AAA\n" assert contents(HOME + '/file3.txt') == "AAA\n" # modify all the repos again contents(r1.remotepath + '/HOMELY.py', template % ('~/file1.txt', 'BBB')) contents(r2.remotepath + '/HOMELY.py', template % ('~/file2.txt', 'BBB')) contents(r3.remotepath + '/HOMELY.py', template % ('~/file3.txt', 'BBB')) # run an update again, but only do it with the 2nd repo system(HOMELY('update') + ['~/repo2']) # 1st and 3rd repos haven't been rerun assert contents(HOME + '/file1.txt') == "AAA\n" assert contents(HOME + '/file3.txt') == "AAA\n" # NOTE that the cleanup of the 2nd repo doesn't happen when we're doing a # single repo assert contents(HOME + '/file2.txt') == "AAA\nBBB\n" # run an update, but specify all repos - this should be enough to trigger # the cleanup system(HOMELY('update') + ['~/repo1', '~/repo3', '~/repo2']) assert contents(HOME + '/file1.txt') == "BBB\n" assert contents(HOME + '/file2.txt') == "BBB\n" assert contents(HOME + '/file3.txt') == "BBB\n" # modify the repos again, but run update with --nopull so that we don't get # any changes contents(r1.remotepath + '/HOMELY.py', template % ('~/file1.txt', 'CCC')) contents(r2.remotepath + '/HOMELY.py', template % ('~/file2.txt', 'CCC')) contents(r3.remotepath + '/HOMELY.py', template % ('~/file3.txt', 'CCC')) system(HOMELY('update') + ['~/repo1', '--nopull']) assert contents(HOME + '/file1.txt') == "BBB\n" assert contents(HOME + '/file2.txt') == "BBB\n" assert contents(HOME + '/file3.txt') == "BBB\n" system(HOMELY('update') + ['--nopull']) assert contents(HOME + '/file1.txt') == "BBB\n" assert contents(HOME + '/file2.txt') == "BBB\n" assert contents(HOME + '/file3.txt') == "BBB\n" # split r1 into multiple sections and just do one of them contents( r1.remotepath + '/HOMELY.py', """ from homely.files import lineinfile from homely.general import section @section def partE(): lineinfile('~/file1.txt', 'EEE') @section def partF(): lineinfile('~/file1.txt', 'FFF') @section def partG(): lineinfile('~/file1.txt', 'GGG') lineinfile('~/file1.txt', 'HHH') """) system(HOMELY('update') + ['~/repo1', '-o', 'partE']) assert contents(HOME + '/file1.txt') == "BBB\nEEE\nHHH\n" os.unlink(HOME + '/file1.txt') system(HOMELY('update') + ['~/repo1', '-o', 'partF', '-o', 'partG']) assert contents(HOME + '/file1.txt') == "FFF\nGGG\nHHH\n" system(HOMELY('update') + ['~/repo1', '-o', 'partF', '-o', 'partG']) assert contents(HOME + '/file1.txt') == "FFF\nGGG\nHHH\n" # ensure that --only isn't allowed with multiple repos system(HOMELY('update') + ['~/repo1', '~/repo2', '-o', 'something'], expecterror=1) # test that cleanup stuff doesn't happen when --only is used system(HOMELY('forget') + ['~/repo2', '~/repo3']) assert os.path.exists(HOME + '/file2.txt') assert os.path.exists(HOME + '/file3.txt') # note that this test also proves that you can use --only without naming a # repo as long as you only have one repo system(HOMELY('update') + ['-o', 'partE']) assert contents(HOME + '/file1.txt') == "FFF\nGGG\nHHH\nEEE\n" # these files are still hanging around because we keep using --only assert os.path.exists(HOME + '/file2.txt') assert os.path.exists(HOME + '/file3.txt') # finally do an update without --only, and file1 and file2 will be cleaned # up system(HOMELY('update')) assert not os.path.exists(HOME + '/file2.txt') assert not os.path.exists(HOME + '/file3.txt')
def test_writefile_usage(tmpdir): from homely._engine2 import Engine from homely.files import LineInFile from homely.general import WriteFile, writefile cfgpath = gettmpfilepath(tmpdir, '.json') # the file we'll be playing with f1 = os.path.join(tmpdir, 'f1.txt') f2 = os.path.join(tmpdir, 'f2.txt') f3 = os.path.join(tmpdir, 'f3.json') # use LineInFile to put stuff in the file e = Engine(cfgpath) e.run(LineInFile(f1, 'AAA')) e.run(LineInFile(f1, 'BBB')) del e assert contents(f1) == "AAA\nBBB\n" # now use WriteFile() to give it new contents e = Engine(cfgpath) e.run(WriteFile(f1, "BBB\nCCC\n")) assert contents(f1) == "BBB\nCCC\n" e.cleanup(e.RAISE) del e # make sure the cleanup didn't blow anything away assert contents(f1) == "BBB\nCCC\n" # make sure the file is removed on cleanup e = Engine(cfgpath) e.cleanup(e.RAISE) del e assert not os.path.exists(f1) contents(f2, "Already here!\n") assert os.path.exists(f2) e = Engine(cfgpath) e.run(WriteFile(f2, "AAA\nBBB\n", canoverwrite=True)) e.cleanup(e.RAISE) del e assert contents(f2) == "AAA\nBBB\n" # running a LineInFile() won't clean up what's already there e = Engine(cfgpath) e.run(LineInFile(f2, "CCC")) e.cleanup(e.RAISE) del e assert contents(f2) == "AAA\nBBB\nCCC\n" # note that removing the LineInFile() doesn't clean up the file because we # no longer know whether the user put their config in there e = Engine(cfgpath) e.cleanup(e.RAISE) del e assert not os.path.exists(f1) assert os.path.exists(f2) # now test the context manager import homely._engine2 e = Engine(cfgpath) homely._engine2._ENGINE = e data = {"z": [3, 4, 5, True], "y": "Hello world", "x": None} with writefile(f3) as f: if sys.version_info[0] < 3: f.write(simplejson.dumps(data, ensure_ascii=False)) else: f.write(simplejson.dumps(data)) e.cleanup(e.RAISE) del e assert os.path.exists(f3) with open(f3, 'r') as f: assert simplejson.loads(f.read()) == data # prove that the WriteFile() disappearing results in the file being removed e = Engine(cfgpath) e.cleanup(e.RAISE) del e assert not os.path.exists(f3)
def test_homely_add_repolist(tmpdir, HOME): system = getsystemfn(HOME) # make a fake repo - create a dir, a folder, and a symlink repo1 = TempRepo(tmpdir, 'repo1') homedir1 = os.path.join(HOME, 'dir1') homelink1 = os.path.join(HOME, 'link1') homedir1file = os.path.join(homelink1, 'file.txt') assert not os.path.exists(homedir1) assert not os.path.islink(homelink1) contents(repo1.remotepath + '/HOMELY.py', """ from homely.files import lineinfile, mkdir, symlink mkdir('~/dir1') symlink('~/dir1', '~/link1') lineinfile('~/link1/file.txt', 'Hello World') """) # add the repo and ensure that everything was created as expected system(HOMELY('add') + [repo1.url]) assert repo1.installedin(HOME) assert os.path.isdir(homedir1) assert os.path.islink(homelink1) assert os.readlink(homelink1) == os.path.realpath(homedir1) assert contents(homedir1file) == "Hello World\n" # make another repo that creates more things repo2 = TempRepo(tmpdir, 'repo2') repo2file = os.path.join(HOME, 'file2.txt') assert not os.path.exists(repo2file) contents(repo2.remotepath + '/HOMELY.py', """ from homely.files import lineinfile lineinfile('~/file2.txt', 'Hey There') """) system(HOMELY('add') + [repo2.url]) assert repo2.installedin(HOME) assert contents(repo2file, "Hey There\n") checkrepolist(HOME, system, [repo1, repo2]) # a 3rd repo, but we're going to clone it into our home dir manually repo3 = TempRepo(tmpdir, 'repo3') contents(repo3.remotepath + '/HOMELY.py', """ from homely.files import lineinfile lineinfile('~/r3.txt', 'From R3') """) # where would it go in the home dir? localrepo3 = os.path.join(HOME, 'repo3') # use a Repo instance to clone it into our home dir manually from homely._vcs.testhandler import Repo Repo.frompath(repo3.url).clonetopath(localrepo3) # test adding a repo from the local dir assert not os.path.exists(HOME + '/r3.txt') system(HOMELY('add') + [localrepo3]) assert contents(HOME + '/r3.txt') == 'From R3\n' checkrepolist(HOME, system, [repo1, repo2, repo3]) # test that you can't add the same repo again system(HOMELY('add') + [repo2.url]) checkrepolist(HOME, system, [repo1, repo2, repo3]) # test that adding a repo with something like 'homely add .' doesn't record # a stupid path like '.' repo4 = TempRepo(tmpdir, 'repo4') contents(repo4.remotepath + '/HOMELY.py', """ from homely.files import lineinfile lineinfile('~/r4.txt', 'From R4') """) localrepo4 = os.path.join(HOME, 'repo4') # use a Repo instance to clone it into our home dir manually from homely._vcs.testhandler import Repo Repo.frompath(repo4.url).clonetopath(localrepo4) system(HOMELY('add') + ['.'], cwd=localrepo4) checkrepolist(HOME, system, [repo1, repo2, repo3, repo4])
def test_homely_add_repolist(tmpdir, HOME): system = getsystemfn(HOME) # make a fake repo - create a dir, a folder, and a symlink repo1 = TempRepo(tmpdir, 'repo1') homedir1 = os.path.join(HOME, 'dir1') homelink1 = os.path.join(HOME, 'link1') homedir1file = os.path.join(homelink1, 'file.txt') assert not os.path.exists(homedir1) assert not os.path.islink(homelink1) contents( repo1.remotepath + '/HOMELY.py', """ from homely.files import lineinfile, mkdir, symlink mkdir('~/dir1') symlink('~/dir1', '~/link1') lineinfile('~/link1/file.txt', 'Hello World') """) # add the repo and ensure that everything was created as expected system(HOMELY('add') + [repo1.url]) assert repo1.installedin(HOME) assert os.path.isdir(homedir1) assert os.path.islink(homelink1) assert os.readlink(homelink1) == os.path.realpath(homedir1) assert contents(homedir1file) == "Hello World\n" # make another repo that creates more things repo2 = TempRepo(tmpdir, 'repo2') repo2file = os.path.join(HOME, 'file2.txt') assert not os.path.exists(repo2file) contents( repo2.remotepath + '/HOMELY.py', """ from homely.files import lineinfile lineinfile('~/file2.txt', 'Hey There') """) system(HOMELY('add') + [repo2.url]) assert repo2.installedin(HOME) assert contents(repo2file, "Hey There\n") checkrepolist(HOME, system, [repo1, repo2]) # a 3rd repo, but we're going to clone it into our home dir manually repo3 = TempRepo(tmpdir, 'repo3') contents( repo3.remotepath + '/HOMELY.py', """ from homely.files import lineinfile lineinfile('~/r3.txt', 'From R3') """) # where would it go in the home dir? localrepo3 = os.path.join(HOME, 'repo3') # use a Repo instance to clone it into our home dir manually from homely._vcs.testhandler import Repo Repo.frompath(repo3.url).clonetopath(localrepo3) # test adding a repo from the local dir assert not os.path.exists(HOME + '/r3.txt') system(HOMELY('add') + [localrepo3]) assert contents(HOME + '/r3.txt') == 'From R3\n' checkrepolist(HOME, system, [repo1, repo2, repo3]) # test that you can't add the same repo again system(HOMELY('add') + [repo2.url]) checkrepolist(HOME, system, [repo1, repo2, repo3]) # test that adding a repo with something like 'homely add .' doesn't record # a stupid path like '.' repo4 = TempRepo(tmpdir, 'repo4') contents( repo4.remotepath + '/HOMELY.py', """ from homely.files import lineinfile lineinfile('~/r4.txt', 'From R4') """) localrepo4 = os.path.join(HOME, 'repo4') # use a Repo instance to clone it into our home dir manually from homely._vcs.testhandler import Repo Repo.frompath(repo4.url).clonetopath(localrepo4) system(HOMELY('add') + ['.'], cwd=localrepo4) checkrepolist(HOME, system, [repo1, repo2, repo3, repo4])
def test_homely_updatestatus(HOME, tmpdir): from homely._utils import RUNFILE, FAILFILE, UpdateStatus, STATUSCODES system = getsystemfn(HOME) jobstart = getjobstartfn(HOME) exit_ok = STATUSCODES[UpdateStatus.OK] exit_never = STATUSCODES[UpdateStatus.NEVER] exit_paused = STATUSCODES[UpdateStatus.PAUSED] exit_failed = STATUSCODES[UpdateStatus.FAILED] exit_running = STATUSCODES[UpdateStatus.RUNNING] # create a special HOMELY.py script that we can control easily by putting magic files in the # right places spinfile = HOME + '/spin' diefile = HOME + '/diediedie' xdir1 = HOME + '/xdir1' assert not os.path.isdir(xdir1) # verify that homely updatestatus thinks we have never run an update system(HOMELY('updatestatus'), expecterror=exit_never) # pause autoupdate, and then check the status again system(HOMELY('autoupdate') + ['--pause']) system(HOMELY('updatestatus'), expecterror=exit_paused) # pausing again don't change anything system(HOMELY('autoupdate') + ['--pause']) system(HOMELY('updatestatus'), expecterror=exit_paused) # unpause takes us back to the previous status system(HOMELY('autoupdate') + ['--unpause']) system(HOMELY('updatestatus'), expecterror=exit_never) # add our special repo with our special file repo1 = TempRepo(tmpdir, 'repo1') contents(repo1.remotepath + '/HOMELY.py', """ import os, time, sys from homely.files import mkdir while os.path.exists(%(spinfile)r): time.sleep(0.01) assert not os.path.exists(%(diefile)r), "Incredibly bad things in %(diefile)s" mkdir('~/xdir1') """ % locals()) system(HOMELY('add') + [repo1.url]) assert os.path.isdir(xdir1) # verify that homely updatestatus thinks we have run an update system(HOMELY('updatestatus')) # use the spinfile to make homely update sit in the background while we test some more things contents(spinfile, "spin!") with jobstart(HOMELY('update')) as job: for _ in waitfor('Appearance of RUNFILE %s' % RUNFILE): if os.path.exists(RUNFILE): break # verify that updatestatus says we are currently running system(HOMELY('updatestatus'), expecterror=exit_running) # remove the spinfile so the background job can finish os.unlink(spinfile) # verify that no update is currently running, and that the last run was successful system(HOMELY('updatestatus')) # now touch the errorfile and try a new update contents(diefile, "boom!") system(HOMELY('update'), expecterror=1) # updatestatus should tell us that the previous run failed system(HOMELY('updatestatus'), expecterror=exit_failed) # the error file should exist assert os.path.exists(FAILFILE) # grab the name of the OUTFILE outfile = system(HOMELY('autoupdate') + ['--outfile']).strip() # note that a plain 'homely update' won't create the outfile for us assert not os.path.exists(outfile) # try and kick off an autoupdate daemon ... it should fail miserably because the previous # update has already failed system(HOMELY('autoupdate') + ['--daemon'], expecterror=1) # use autoupdate --reset to allow automatic updates to resume system(HOMELY('autoupdate') + ['--clear']) system(HOMELY('updatestatus'), expecterror=exit_ok) # try and kick off an autoupdate daemon again ... it should fail miserably because the previous # update was too recent system(HOMELY('autoupdate') + ['--daemon'], expecterror=1) # remove the TIMEFILE so that homely thinks an update has never been run before from homely._utils import TIMEFILE os.unlink(TIMEFILE) try: # we use the spinfile to make sure the next autoupdate is going to stall contents(spinfile, "spin!") contents(diefile, "boom!") system(HOMELY('autoupdate') + ['--daemon']) # wait for the RUNFILE to appear for _ in waitfor('Appearance of RUNFILE'): if os.path.exists(RUNFILE): break # assert that our status is "running" system(HOMELY('updatestatus'), expecterror=exit_running) finally: # remove the spinfile so that the daemon can finish os.unlink(spinfile) # wait for the runfile to disappear for _ in waitfor('Disappearance of RUNFILE %s' % RUNFILE): if not os.path.exists(RUNFILE): break # the autoupdate should have failed because of the diefile system(HOMELY('updatestatus'), expecterror=exit_failed) with open(outfile) as f: assert 'Incredibly bad things' in f.read() # let's do this again, but without the diefile os.unlink(diefile) system(HOMELY('autoupdate') + ['--clear']) # we also need to manually remove the timefile os.unlink(TIMEFILE) try: contents(spinfile, "spin!") system(HOMELY('autoupdate') + ['--daemon']) for _ in waitfor('Appearance of RUNFILE'): if os.path.exists(RUNFILE): break system(HOMELY('updatestatus'), expecterror=exit_running) finally: os.unlink(spinfile) for _ in waitfor('Disappearance of RUNFILE %s' % RUNFILE): if not os.path.exists(RUNFILE): break # updatestatus should show that the update was successful system(HOMELY('updatestatus'), expecterror=exit_ok)
def test_lineinfile_cleanup_interaction(tmpdir): from homely._engine2 import Engine from homely.files import MakeDir, LineInFile # a temporary file where the engine can store its config cfgpath = gettmpfilepath(tmpdir, '.json') f1 = os.path.join(tmpdir, 'f1.txt') d1 = os.path.join(tmpdir, 'f1.txt.dir') d1f1 = os.path.join(d1, 'f-1.txt') d2 = os.path.join(tmpdir, 'dir2') d2f1 = os.path.join(d2, 'f-1.txt') d3 = os.path.join(tmpdir, 'dir3') d3d1 = os.path.join(d3, 'sub-1') d3d2 = os.path.join(d3, 'sub-2') d3d1f1 = os.path.join(d3d1, 'somefile.txt') d3d2f1 = os.path.join(d3d2, 'somefile.txt') # make the dir and the file, make sure they both exist e = Engine(cfgpath) e.run(LineInFile(f1, "AAA")) e.run(MakeDir(d1)) e.cleanup(e.RAISE) del e assert os.path.isdir(d1) assert contents(f1) == "AAA\n" # remake the engine without the dir, make sure the dir isn't kept around by # the file e = Engine(cfgpath) e.run(LineInFile(f1, "AAA")) e.cleanup(e.POSTPONE) del e assert not os.path.exists(d1) assert contents(f1) == "AAA\n" # make a new file in the directory, make sure they take ownership of the # directory e = Engine(cfgpath) e.run(MakeDir(d1)) e.run(LineInFile(d1f1, "AAA")) e.run(LineInFile(d1f1, "BBB")) e.cleanup(e.POSTPONE) del e # the directory should still exist, the old file should not, and the new # file should contain both lines assert os.path.isdir(d1) assert not os.path.exists(f1) assert contents(d1f1) == "AAA\nBBB\n" # if we do the same thing again but without the MakeDir, we should get # exactly the same result e = Engine(cfgpath) e.run(LineInFile(d1f1, "AAA")) e.run(LineInFile(d1f1, "BBB")) e.cleanup(e.POSTPONE) del e # the directory should still exist, the old file should not, and the new # file should contain both lines assert os.path.isdir(d1) assert not os.path.exists(f1) assert contents(d1f1) == "AAA\nBBB\n" # the important thing to note is that the engine still knows that it needs # to clean up the directory later e = Engine(cfgpath) assert e.pathstoclean()[d1] == e.TYPE_FOLDER_ONLY del e # if we get rid of one of the LineInFile() items, the file and dir still # hang around e = Engine(cfgpath) e.run(LineInFile(d1f1, "AAA")) e.cleanup(e.POSTPONE) del e assert contents(d1f1) == "AAA\n" # now, when we get rid of the last LineInFile() items, everything will be # cleaned up e = Engine(cfgpath) e.cleanup(e.RAISE) del e assert not os.path.exists(d1) os.unlink(cfgpath) # if we make a dir ourselves, a LineInFile() will not clean it up assert not os.path.exists(d2) os.mkdir(d2) e = Engine(cfgpath) e.run(LineInFile(d2f1, "AAA")) del e assert contents(d2f1) == "AAA\n" e = Engine(cfgpath) e.cleanup(e.RAISE) del e assert os.path.isdir(d2) and not os.path.exists(d2f1) os.rmdir(d2) # if we make a file ourselves, LineInFile() will not try to clean it up assert not os.path.exists(f1) contents(f1, "") e = Engine(cfgpath) e.run(LineInFile(f1, "AAA")) del e assert contents(f1) == "AAA\n" e = Engine(cfgpath) e.cleanup(e.RAISE) del e assert contents(f1) == "" # if two LineInFile() items share a parent dir (even the parent dir's # parent, and so on), and that parent dir was made by a MakeDir(), then it # gets cleaned up eventually when both of the LineInFile() items go away # - create the folder structure assert not os.path.exists(d3) os.unlink(cfgpath) e = Engine(cfgpath) e.run(MakeDir(d3)) e.run(MakeDir(d3d1)) e.run(MakeDir(d3d2)) e.cleanup(e.RAISE) del e assert all(map(os.path.isdir, [d3, d3d1, d3d2])) assert not any(map(os.path.exists, [d3d1f1, d3d2f1])) # - scrap the folder structure, but make files that depend on those folders e = Engine(cfgpath) e.run(LineInFile(d3d1f1, "AAA")) e.run(LineInFile(d3d2f1, "BBB")) e.cleanup(e.POSTPONE) del e assert contents(d3d1f1) == "AAA\n" and contents(d3d2f1) == "BBB\n" # - scrap the first file, and check that only things needed for the 2nd # file remain e = Engine(cfgpath) e.run(LineInFile(d3d2f1, "BBB")) e.cleanup(e.POSTPONE) del e assert not os.path.exists(d3d1) assert contents(d3d2f1) == "BBB\n" # - scrap the 2nd file and make sure everything else disappears e = Engine(cfgpath) e.cleanup(e.RAISE) del e assert not os.path.exists(d3)
def test_homely_update(HOME, tmpdir): system = getsystemfn(HOME) def _addfake(name, createfile): # create a fake repo and add it tr = TempRepo(tmpdir, name) tf = os.path.join(HOME, createfile) contents(tr.remotepath + '/HOMELY.py', """ from homely.files import lineinfile lineinfile('~/%s', 'Hello from %s') """ % (createfile, name)) assert not os.path.exists(tf) system(HOMELY('add') + [tr.url]) assert contents(tf) == "Hello from %s\n" % name return tr template = (""" from homely.files import lineinfile lineinfile(%r, %r) """) # add some fake repos r1 = _addfake('repo1', 'file1.txt') r2 = _addfake('repo2', 'file2.txt') r3 = _addfake('repo3', 'file3.txt') # update the repos slightly, run an update, ensure all changes have gone # through (including cleanup of old content) contents(r1.remotepath + '/HOMELY.py', template % ('~/file1.txt', 'AAA')) contents(r2.remotepath + '/HOMELY.py', template % ('~/file2.txt', 'AAA')) contents(r3.remotepath + '/HOMELY.py', template % ('~/file3.txt', 'AAA')) system(HOMELY('update')) assert contents(HOME + '/file1.txt') == "AAA\n" assert contents(HOME + '/file2.txt') == "AAA\n" assert contents(HOME + '/file3.txt') == "AAA\n" # modify all the repos again contents(r1.remotepath + '/HOMELY.py', template % ('~/file1.txt', 'BBB')) contents(r2.remotepath + '/HOMELY.py', template % ('~/file2.txt', 'BBB')) contents(r3.remotepath + '/HOMELY.py', template % ('~/file3.txt', 'BBB')) # run an update again, but only do it with the 2nd repo system(HOMELY('update') + ['~/repo2']) # 1st and 3rd repos haven't been rerun assert contents(HOME + '/file1.txt') == "AAA\n" assert contents(HOME + '/file3.txt') == "AAA\n" # NOTE that the cleanup of the 2nd repo doesn't happen when we're doing a # single repo assert contents(HOME + '/file2.txt') == "AAA\nBBB\n" # run an update, but specify all repos - this should be enough to trigger # the cleanup system(HOMELY('update') + ['~/repo1', '~/repo3', '~/repo2']) assert contents(HOME + '/file1.txt') == "BBB\n" assert contents(HOME + '/file2.txt') == "BBB\n" assert contents(HOME + '/file3.txt') == "BBB\n" # modify the repos again, but run update with --nopull so that we don't get # any changes contents(r1.remotepath + '/HOMELY.py', template % ('~/file1.txt', 'CCC')) contents(r2.remotepath + '/HOMELY.py', template % ('~/file2.txt', 'CCC')) contents(r3.remotepath + '/HOMELY.py', template % ('~/file3.txt', 'CCC')) system(HOMELY('update') + ['~/repo1', '--nopull']) assert contents(HOME + '/file1.txt') == "BBB\n" assert contents(HOME + '/file2.txt') == "BBB\n" assert contents(HOME + '/file3.txt') == "BBB\n" system(HOMELY('update') + ['--nopull']) assert contents(HOME + '/file1.txt') == "BBB\n" assert contents(HOME + '/file2.txt') == "BBB\n" assert contents(HOME + '/file3.txt') == "BBB\n" # split r1 into multiple sections and just do one of them contents(r1.remotepath + '/HOMELY.py', """ from homely.files import lineinfile from homely.general import section @section def partE(): lineinfile('~/file1.txt', 'EEE') @section def partF(): lineinfile('~/file1.txt', 'FFF') @section def partG(): lineinfile('~/file1.txt', 'GGG') lineinfile('~/file1.txt', 'HHH') """) system(HOMELY('update') + ['~/repo1', '-o', 'partE']) assert contents(HOME + '/file1.txt') == "BBB\nEEE\nHHH\n" os.unlink(HOME + '/file1.txt') system(HOMELY('update') + ['~/repo1', '-o', 'partF', '-o', 'partG']) assert contents(HOME + '/file1.txt') == "FFF\nGGG\nHHH\n" system(HOMELY('update') + ['~/repo1', '-o', 'partF', '-o', 'partG']) assert contents(HOME + '/file1.txt') == "FFF\nGGG\nHHH\n" # ensure that --only isn't allowed with multiple repos system(HOMELY('update') + ['~/repo1', '~/repo2', '-o', 'something'], expecterror=1) # test that cleanup stuff doesn't happen when --only is used system(HOMELY('forget') + ['~/repo2', '~/repo3']) assert os.path.exists(HOME + '/file2.txt') assert os.path.exists(HOME + '/file3.txt') # note that this test also proves that you can use --only without naming a # repo as long as you only have one repo system(HOMELY('update') + ['-o', 'partE']) assert contents(HOME + '/file1.txt') == "FFF\nGGG\nHHH\nEEE\n" # these files are still hanging around because we keep using --only assert os.path.exists(HOME + '/file2.txt') assert os.path.exists(HOME + '/file3.txt') # finally do an update without --only, and file1 and file2 will be cleaned # up system(HOMELY('update')) assert not os.path.exists(HOME + '/file2.txt') assert not os.path.exists(HOME + '/file3.txt')