""" author: deadc0de6 (https://github.com/deadc0de6) Copyright (c) 2017, deadc0de6 basic unittest for the install function """ import os import unittest from unittest.mock import MagicMock import filecmp from tests.helpers import (clean, create_dir, create_fake_config, create_random_file, get_string, get_tempdir, load_options, populate_fake_config) from dotdrop.cfg_aggregator import CfgAggregator as Cfg from dotdrop.dotfile import Dotfile from dotdrop.installer import Installer from dotdrop.action import Action from dotdrop.dotdrop import cmd_install from dotdrop.options import BACKUP_SUFFIX from dotdrop.utils import header from dotdrop.linktypes import LinkTypes def fake_config(path, dotfiles, profile, dotpath, actions, transs): """Create a fake config file""" with open(path, 'w', encoding='utf-8') as file: file.write('actions:\n') for action in actions: file.write(f' {action.key}: {action.action}\n') file.write('trans_install:\n') for trans in transs: file.write(f' {trans.key}: {trans.action}\n') file.write('config:\n') file.write(' backup: true\n') file.write(' create: true\n') file.write(f' dotpath: {dotpath}\n') file.write('dotfiles:\n') for dotfile in dotfiles: linkval = dotfile.link.name.lower() file.write(f' {dotfile.key}:\n') file.write(f' dst: {dotfile.dst}\n') file.write(f' src: {dotfile.src}\n') file.write(f' link: {linkval}\n') if len(dotfile.actions) > 0: file.write(' actions:\n') for action in dotfile.actions: file.write(f' - {action.key}\n') if dotfile.trans_install: for trans in dotfile.trans_install: file.write(f' trans_install: {trans.key}\n') file.write('profiles:\n') file.write(f' {profile}:\n') file.write(' dotfiles:\n') for dotfile in dotfiles: file.write(f' - {dotfile.key}\n') return path def get_workdir(): """get the workdir""" workdir = '~/.config/dotdrop' workdir = os.path.expanduser(workdir) workdir = os.path.normpath(workdir) return workdir class TestInstall(unittest.TestCase): """test case""" CONFIG_NAME = 'config.yaml' TEMPLATE = ''' # launch the wm {%@@ if profile == "home" @@%} exec awesome {%@@ else @@%} exec bspwm {%@@ endif @@%} ''' RESULT = ''' # launch the wm exec bspwm ''' def test_install(self): """Test the install function""" # dotpath location tmp = get_tempdir() self.assertTrue(os.path.exists(tmp)) self.addCleanup(clean, tmp) # where dotfiles will be installed dst = get_tempdir() self.assertTrue(os.path.exists(dst)) self.addCleanup(clean, dst) # create the dotfile in dotdrop fcontent1, _ = create_random_file(tmp) dst1 = os.path.join(dst, get_string(6)) dotfile1 = Dotfile(get_string(5), dst1, os.path.basename(fcontent1)) # fake a __str__ self.assertTrue(str(dotfile1) != '') fcontent2, _ = create_random_file(tmp) dst2 = os.path.join(dst, get_string(6)) dotfile2 = Dotfile(get_string(5), dst2, os.path.basename(fcontent2)) with open(fcontent2, 'w', encoding='utf-8') as file: file.write(self.TEMPLATE) fcontent3, _ = create_random_file(tmp, binary=True) dst3 = os.path.join(dst, get_string(6)) dotfile3 = Dotfile(get_string(5), dst3, os.path.basename(fcontent3)) # create a directory dotfile dir1 = os.path.join(tmp, 'somedir') create_dir(dir1) fildfd, _ = create_random_file(dir1) dstd = os.path.join(dst, get_string(6)) ddot = Dotfile(get_string(5), dstd, os.path.basename(dir1)) # to test backup fcontent4, _ = create_random_file(tmp) dst4 = os.path.join(dst, get_string(6)) dotfile4 = Dotfile(key=get_string(6), dst=dst4, src=os.path.basename(fcontent4)) with open(dst4, 'w', encoding='utf-8') as file: file.write(get_string(16)) # to test link fcontent5, _ = create_random_file(tmp) dst5 = os.path.join(dst, get_string(6)) self.addCleanup(clean, dst5) dotfile5 = Dotfile(get_string(6), dst5, os.path.basename(fcontent5), link=LinkTypes.LINK) # create the dotfile directories in dotdrop dir1 = create_dir(os.path.join(tmp, get_string(6))) self.assertTrue(os.path.exists(dir1)) self.addCleanup(clean, dir1) dst6 = os.path.join(dst, get_string(6)) # fill with files sub1, _ = create_random_file(dir1, template=True) self.assertTrue(os.path.exists(sub1)) sub2, _ = create_random_file(dir1) self.assertTrue(os.path.exists(sub2)) # make up the dotfile dotfile6 = Dotfile(get_string(6), dst6, os.path.basename(dir1)) # to test symlink directories dir2 = create_dir(os.path.join(tmp, get_string(6))) self.assertTrue(os.path.exists(dir2)) self.addCleanup(clean, dir2) dst7 = os.path.join(dst, get_string(6)) # fill with files sub3, _ = create_random_file(dir2) self.assertTrue(os.path.exists(sub3)) sub4, _ = create_random_file(dir2) self.assertTrue(os.path.exists(sub4)) # make up the dotfile dotfile7 = Dotfile(get_string(6), dst7, os.path.basename(dir2), link=LinkTypes.LINK) # to test actions value = get_string(12) fact = '/tmp/action' self.addCleanup(clean, fact) act1 = Action('testaction', 'post', f'echo "{value}" > {fact}') fcontent8, _ = create_random_file(tmp) dst8 = os.path.join(dst, get_string(6)) dotfile8 = Dotfile(get_string(6), dst8, os.path.basename(fcontent8), actions=[act1]) # to test transformations trans1 = 'trans1' trans2 = 'trans2' # pylint: disable=C0209 cmd = 'cat {0} | sed \'s/%s/%s/g\' > {1}' % (trans1, trans2) self.addCleanup(clean, trans2) the_trans = Action('testtrans', 'post', cmd) fcontent9, _ = create_random_file(tmp, content=trans1) dst9 = os.path.join(dst, get_string(6)) dotfile9 = Dotfile(get_string(6), dst9, os.path.basename(fcontent9), trans_install=[the_trans]) # to test template f10, _ = create_random_file(tmp, content='{{@@ header() @@}}') dst10 = os.path.join(dst, get_string(6)) dotfile10 = Dotfile(get_string(6), dst10, os.path.basename(f10)) # generate the config and stuff profile = get_string(5) confpath = os.path.join(tmp, self.CONFIG_NAME) dotfiles = [dotfile1, dotfile2, dotfile3, dotfile4, dotfile5, dotfile6, dotfile7, dotfile8, dotfile9, dotfile10, ddot] fake_config(confpath, dotfiles, profile, tmp, [act1], [the_trans]) conf = Cfg(confpath, profile, debug=True) self.assertTrue(conf is not None) # install them opt = load_options(confpath, profile) opt.safe = False opt.install_showdiff = True cmd_install(opt) # now compare the generated files self.assertTrue(os.path.exists(dst1)) self.assertTrue(os.path.exists(dst2)) self.assertTrue(os.path.exists(dst3)) self.assertTrue(os.path.exists(dst5)) self.assertTrue(os.path.exists(dst6)) self.assertTrue(os.path.exists(dst7)) self.assertTrue(os.path.exists(dst8)) self.assertTrue(os.path.exists(dst10)) self.assertTrue(os.path.exists(fildfd)) # check if 'dst5' is a link whose target is 'f5' self.assertTrue(os.path.islink(dst5)) self.assertTrue(os.path.realpath(dst5) == os.path.realpath(fcontent5)) # check if 'dst7' is a link whose target is 'dir2' self.assertTrue(os.path.islink(dst7)) self.assertTrue(os.path.realpath(dst7) == os.path.realpath(dir2)) # make sure backup is there backupf = dst4 + BACKUP_SUFFIX self.assertTrue(os.path.exists(backupf)) self.assertTrue(filecmp.cmp(fcontent1, dst1, shallow=True)) f2content = '' with open(dst2, 'r', encoding='utf-8') as file: f2content = file.read() self.assertTrue(f2content == self.RESULT) self.assertTrue(filecmp.cmp(fcontent3, dst3, shallow=True)) # test action has been executed self.assertTrue(os.path.exists(fact)) self.assertTrue(str(act1) != '') actcontent = '' with open(fact, 'r', encoding='utf-8') as file: actcontent = file.read().rstrip() self.assertTrue(actcontent == value) # test transformation has been done self.assertTrue(os.path.exists(dst9)) transcontent = '' with open(dst9, 'r', encoding='utf-8') as file: transcontent = file.read().rstrip() self.assertTrue(transcontent == trans2) # test template has been remplaced self.assertTrue(os.path.exists(dst10)) tempcontent = '' with open(dst10, 'r', encoding='utf-8') as file: tempcontent = file.read().rstrip() self.assertTrue(tempcontent == header()) def test_install_import_configs(self): """Test the install function with imported configs""" # dotpath location tmp = get_tempdir() self.assertTrue(os.path.exists(tmp)) self.addCleanup(clean, tmp) os.mkdir(os.path.join(tmp, 'importing')) os.mkdir(os.path.join(tmp, 'imported')) # where dotfiles will be installed dst = get_tempdir() self.assertTrue(os.path.exists(dst)) self.addCleanup(clean, dst) # creating random dotfiles imported_dotfile, _ = create_random_file(os.path.join(tmp, 'imported')) imported_dotfile = { 'dst': os.path.join(dst, imported_dotfile), 'key': f'f_{imported_dotfile}', 'name': imported_dotfile, 'src': os.path.join(tmp, 'imported', imported_dotfile), } importing_dotfile, _ = \ create_random_file(os.path.join(tmp, 'importing')) importing_dotfile = { 'dst': os.path.join(dst, importing_dotfile), 'key': f'f_{importing_dotfile}', 'name': importing_dotfile, 'src': os.path.join(tmp, 'imported', importing_dotfile), } imported = { 'config': { 'dotpath': 'imported', }, 'dotfiles': { imported_dotfile['key']: { 'dst': imported_dotfile['dst'], 'src': imported_dotfile['name'], }, }, 'profiles': { 'host1': { 'dotfiles': [imported_dotfile['key']], }, }, } importing = { 'config': { 'dotpath': 'importing', }, 'dotfiles': { importing_dotfile['key']: { 'dst': importing_dotfile['dst'], 'src': importing_dotfile['src'], }, }, 'profiles': { 'host2': { 'dotfiles': [importing_dotfile['key']], 'include': ['host1'], }, }, } # create the imported base config file imported_path = create_fake_config(tmp, configname='config-2.yaml', **imported['config']) # create the importing base config file importing_path = create_fake_config(tmp, configname='config.yaml', import_configs=['config-2.yaml'], **importing['config']) # edit the imported config populate_fake_config(imported_path, **{ k: v for k, v in imported.items() if k != 'config' }) # edit the importing config populate_fake_config(importing_path, **{ k: v for k, v in importing.items() if k != 'config' }) # install them opt = load_options(importing_path, 'host2') opt.safe = False opt.install_showdiff = True opt.variables = {} cmd_install(opt) # now compare the generated files self.assertTrue(os.path.exists(importing_dotfile['dst'])) self.assertTrue(os.path.exists(imported_dotfile['dst'])) def test_link_children(self): """test the link children""" # create workdir work_dir = get_tempdir() self.assertTrue(os.path.exists(work_dir)) self.addCleanup(clean, work_dir) # create source dir src_dir = get_tempdir() self.assertTrue(os.path.exists(src_dir)) self.addCleanup(clean, src_dir) # where dotfiles will be installed dst_dir = get_tempdir() self.assertTrue(os.path.exists(dst_dir)) self.addCleanup(clean, dst_dir) # create 3 random files in source srcs = [create_random_file(src_dir)[0] for _ in range(3)] installer = Installer(workdir=work_dir) installer.install(templater=MagicMock(), src=src_dir, dst=dst_dir, linktype=LinkTypes.LINK_CHILDREN, actionexec=None) # Ensure all destination files point to source for src in srcs: xyz = os.path.join(dst_dir, src) self.assertEqual(os.path.realpath(xyz), os.path.realpath(src)) def test_fails_without_src(self): """test fails without src""" src = '/some/non/existant/file' installer = Installer() # logger = MagicMock() # installer.log.err = logger res, err = installer.install(templater=MagicMock(), src=src, dst='/dev/null', linktype=LinkTypes.LINK_CHILDREN, actionexec=None) self.assertFalse(res) exp = f'source dotfile does not exist: {src}' self.assertEqual(err, exp) def test_fails_when_src_file(self): """test fails when src file""" # create source dir src_dir = get_tempdir() self.assertTrue(os.path.exists(src_dir)) self.addCleanup(clean, src_dir) src = create_random_file(src_dir)[0] # logger = MagicMock() templater = MagicMock() installer = Installer() # installer.log.err = logger # pass src file not src dir res, err = installer.install(templater=templater, src=src, dst='/dev/null', linktype=LinkTypes.LINK_CHILDREN, actionexec=None) # ensure nothing performed self.assertFalse(res) exp = f'source dotfile is not a directory: {src}' self.assertEqual(err, exp) def test_creates_dst(self): """test creates dst""" src_dir = get_tempdir() self.assertTrue(os.path.exists(src_dir)) self.addCleanup(clean, src_dir) # where dotfiles will be installed dst_dir = get_tempdir() self.addCleanup(clean, dst_dir) # move dst dir to new (uncreated) dir in dst dst_dir = os.path.join(dst_dir, get_string(6)) self.assertFalse(os.path.exists(dst_dir)) installer = Installer() installer.install(templater=MagicMock(), src=src_dir, dst=dst_dir, linktype=LinkTypes.LINK_CHILDREN, actionexec=None) # ensure dst dir created self.assertTrue(os.path.exists(dst_dir)) def test_prompts_to_replace_dst(self): """test prompts to replace dst""" # create source dir src_dir = get_tempdir() self.assertTrue(os.path.exists(src_dir)) self.addCleanup(clean, src_dir) # where dotfiles will be installed dst_dir = get_tempdir() self.addCleanup(clean, dst_dir) # Create destination file to be replaced dst = os.path.join(dst_dir, get_string(6)) with open(dst, 'w', encoding='utf=8'): pass self.assertTrue(os.path.isfile(dst)) # setup mocks ask = MagicMock() ask.return_value = True # setup installer installer = Installer() installer.safe = True installer.log.ask = ask installer.install(templater=MagicMock(), src=src_dir, dst=dst, linktype=LinkTypes.LINK_CHILDREN, actionexec=None) # ensure destination now a directory self.assertTrue(os.path.isdir(dst)) # ensure prompted ask.assert_called_with( f'Remove regular file {dst} and replace with empty directory?') def test_runs_templater(self): """test runs templater""" # create workdir work_dir = get_tempdir() self.assertTrue(os.path.exists(work_dir)) self.addCleanup(clean, work_dir) # create source dir src_dir = get_tempdir() self.assertTrue(os.path.exists(src_dir)) self.addCleanup(clean, src_dir) # where dotfiles will be installed dst_dir = get_tempdir() self.assertTrue(os.path.exists(dst_dir)) self.addCleanup(clean, dst_dir) # create 3 random files in source srcs = [create_random_file(src_dir)[0] for _ in range(3)] # setup installer and mocks installer = Installer(workdir=work_dir) templater = MagicMock() templater.generate.return_value = b'content' installer.install(templater=templater, src=src_dir, dst=dst_dir, linktype=LinkTypes.LINK_CHILDREN, actionexec=None) for src in srcs: xyz = os.path.join(dst_dir, os.path.basename(src)) # ensure dst is link self.assertTrue(os.path.islink(xyz)) # ensure dst not directly linked to src self.assertNotEqual(os.path.realpath(xyz), src) def main(): """entry point""" unittest.main() if __name__ == '__main__': main()