Skip to content

Commit

Permalink
added unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
clintmod committed Nov 17, 2017
1 parent b1da512 commit a8a511e
Show file tree
Hide file tree
Showing 22 changed files with 413 additions and 53 deletions.
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
*.pyc
*.pyc
.cache
.tmontmp
.testmondata
macprefsc
.coverage
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ confidence=
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,C0111
disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,C0111,C0304,C0103

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
Expand Down
11 changes: 7 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"cSpell.words": [
"cSpell.words": [
"macprefs",
"plist",
"pytest",
"symlinks"
],
],
"python.unitTest.pyTestArgs": [
"."
],
"python.unitTest.pyTestEnabled": true
}
"python.unitTest.pyTestEnabled": false
}
18 changes: 18 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Run All Tests with watch",
"type": "shell",
"command": "ptw --onfail 'say failed' -- --testmon ",
"isBackground": true,
"presentation": {
"echo":false,
"reveal": "never"
},
"problemMatcher": []
}
]
}
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
clean:
find . -name '*.pyc' -delete

setup:
pip install -r requirements.txt

test:
pytest --cov=.

lint:
pylint *.py

publish:


help:
@echo "COMMANDS:"
@echo " clean Remove all generated files."
@echo " setup Setup development environment."
@echo " test Run tests."
@echo " lint Run analysis tools."
@echo " publish Tag and push to github and update the brew formula with the new url and sha256 and push to github
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,21 @@ sudo macprefs restore

## Problems

- If you find a problem or a have a question feel free to file a bug here and/or send a pull request and I'll be happy to look at it and/or merge it.
- If you find a problem or a have a question feel free to file a bug here and/or send a pull request and I'll be happy to look at it and/or merge it.

## Contributing

### Getting started

- Fork and clone then cd to this git repo
- Run `pip install -r requirements.txt`

### Running the tests

- Run `make test lint` (make sure you've done the [Getting Started](#getting-started))

### Getting your changes merged

- Make your changes and push them to github
- Make sure your changes have tests and pass linting
- Open a pull request
8 changes: 2 additions & 6 deletions backup_preferences.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
from config import BACKUP_DIR
import config
from utils import execute_shell


Expand All @@ -8,11 +7,8 @@ def backup():
domains = domains.split("\n")[0].split(", ")
domains = ["NSGlobalDomain"] + domains

if not os.path.exists(BACKUP_DIR):
os.makedirs(BACKUP_DIR)

for domain in domains:
filepath = BACKUP_DIR + domain + ".plist"
filepath = config.get_backup_file_path(domain)
print "Backing up: " + domain + " to " + filepath
execute_shell(["defaults", "export", domain, filepath])

Expand Down
21 changes: 13 additions & 8 deletions backup_system_preferences.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import os
from os import path
from utils import execute_shell
from config import BACKUP_DIR
from config import get_backup_dir


def backup():
power_management_backup_path = os.path.join(
BACKUP_DIR, "System", "com.apple.PowerManagement.plist")
power_management_domain = "/Library/Preferences/com.apple.PowerManagement"
power_management_domain = get_domain()
power_management_backup_path = path.join(
get_backup_dir(), "System", "com.apple.PowerManagement.plist")
# On older versions of Mac OS X PowerManagement lived under SystemConfiguration
if not os.path.exists(power_management_domain + ".plist"):
power_management_domain = "/Library/Preferences/SystemConfiguration/com.apple.PowerManagement"
print "Backing up: " + power_management_domain + " to " + power_management_backup_path
# sudo is not required to back up but it is to restore
execute_shell(["defaults", "export", power_management_domain,
power_management_backup_path], False)
power_management_backup_path], False)


def get_domain():
power_management_domain = "/Library/Preferences/com.apple.PowerManagement"
if not path.exists(power_management_domain + ".plist"):
power_management_domain = "/Library/Preferences/SystemConfiguration/com.apple.PowerManagement"
return power_management_domain


if __name__ == '__main__':
Expand Down
17 changes: 15 additions & 2 deletions config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import os
from os import path

BACKUP_DIR = os.environ['MACPREFS_BACKUP_DIR'] if 'MACPREFS_BACKUP_DIR' in os.environ else path.join(
path.expanduser("~"), "Dropbox", "MacPrefsBackup")

def get_backup_dir():
backup_dir = ""
if 'MACPREFS_BACKUP_DIR' in os.environ:
backup_dir = os.environ['MACPREFS_BACKUP_DIR']
else:
backup_dir = path.join(path.expanduser(
"~"), "Dropbox", "MacPrefsBackup")
if not os.path.exists(backup_dir):
os.makedirs(backup_dir)
return backup_dir


def get_backup_file_path(domain):
return path.join(get_backup_dir(), domain + ".plist")
29 changes: 14 additions & 15 deletions macprefs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import backup_system_preferences
import restore_preferences
import restore_system_preferences


def backup():
backup_system_preferences.backup()
backup_preferences.backup()
Expand All @@ -18,20 +17,20 @@ def restore():
restore_system_preferences.restore()
restore_preferences.restore()


if __name__ == '__main__':
PARSER = argparse.ArgumentParser(
backup_dir = config.get_backup_dir()
parser = argparse.ArgumentParser(
prog="macprefs", description='Backup and restore mac system preferences.')
SUBPARSERS = PARSER.add_subparsers(title="commands", metavar='')
BACKUP_PARSER = SUBPARSERS.add_parser(
'backup', help="Backup mac preferences to " + config.BACKUP_DIR)
BACKUP_PARSER.set_defaults(func=backup)
RESTORE_PARSER = SUBPARSERS.add_parser(
'restore', help="Restore mac preferences from " + config.BACKUP_DIR)
RESTORE_PARSER.set_defaults(func=restore)
subparsers = parser.add_subparsers(title="commands", metavar='')
backup_parser = subparsers.add_parser(
'backup', help="Backup mac preferences to " + backup_dir)
backup_parser.set_defaults(func=backup)
restore_parser = subparsers.add_parser(
'restore', help="Restore mac preferences from " + backup_dir)
restore_parser.set_defaults(func=restore)
if len(sys.argv) == 1:
PARSER.print_help()
sys.exit(1)
ARGS = PARSER.parse_args()
if ARGS.func is not None:
ARGS.func()
parser.print_help()
sys.exit(0)
args = parser.parse_args()
if args.func is not None:
args.func()
8 changes: 8 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# pytest.ini

[pytest]
addopts = --maxfail=2 -s --tb=native -v -m "not integration"
markers = integration: integration tests

[pytest-watch]
nobeep = True
8 changes: 8 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Needed to run the tests
# Run "pip install -r requirements.txt" then pytest
mock
pylint
pytest
pytest-cov
pytest-testmon
pytest-watch
22 changes: 15 additions & 7 deletions restore_preferences.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import os
from config import BACKUP_DIR
from config import get_backup_dir
from utils import execute_shell


def restore():
for filename in sorted(os.listdir(BACKUP_DIR)):
if not ".plist" in filename:
continue
domain = filename.replace(".plist", "")
backup_dir = get_backup_dir()
domains = get_domains()
for domain in domains:
filename = domain + ".plist"
print "Importing: " + domain
execute_shell(["defaults", "import", domain,
os.path.join(BACKUP_DIR, filename)])
os.path.join(backup_dir, filename)])


def get_domains():
domains = []
backup_dir = get_backup_dir()
for filename in sorted(os.listdir(backup_dir)):
if ".plist" in filename:
domains.append(filename.replace(".plist", ""))
return domains


if __name__ == '__main__':
Expand Down
16 changes: 9 additions & 7 deletions restore_system_preferences.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import os
import sys
from config import BACKUP_DIR
from config import get_backup_dir
from utils import execute_shell

power_management_restore_path = os.path.join(
get_backup_dir(), "System", "com.apple.PowerManagement.plist")

power_management_domain = "/Library/Preferences/com.apple.PowerManagement"


def restore():
if os.getuid() != 0:
print "sudo is required to restore preferences: (e.g. sudo " + sys.argv[0] + " restore)"
sys.exit()
power_management_restore_path = os.path.join(
BACKUP_DIR, "System", "com.apple.PowerManagement.plist")
power_management_domain = "/Library/Preferences/com.apple.PowerManagement"
print "Error: sudo is required to restore preferences: (e.g. sudo " + sys.argv[0] + " restore)"
sys.exit(1)
print "Restoring: " + power_management_domain + " from " + power_management_restore_path
execute_shell(["defaults", "import", power_management_domain,
power_management_restore_path], False)
power_management_restore_path])


if __name__ == '__main__':
Expand Down
39 changes: 39 additions & 0 deletions test_backup_preferences.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from os import path
from mock import patch

import backup_preferences
from config import get_backup_dir


@patch("backup_preferences.execute_shell")
def test_backup(execute_shell_mock):
execute_shell_mock.side_effect = execute_shell_func
backup_preferences.backup()


def execute_shell_func(*args):
command = args[0]
if command[1] == "domains":
return domains_func(command)
if command[1] == "export":
return exports_func(command)


def domains_func(command):
assert isinstance(command, list)
assert len(command) == 2
assert command[0] == "defaults"
assert command[1] == "domains"
return ", ".join(["asdf.com"])


def exports_func(command):
assert isinstance(command, list)
assert len(command) == 4
assert command[0] == "defaults"
assert command[1] == "export"
assert "NSGlobalDomain" in command[2] or "asdf.com" in command[2]
backup_dir = get_backup_dir()
nsgd_file = path.join(backup_dir, "/NSGlobalDomain.plist")
asdf_file = path.join(backup_dir, "asdf.com.plist")
assert nsgd_file in command[3] or asdf_file in command[3]
16 changes: 16 additions & 0 deletions test_backup_system_preferences.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import backup_system_preferences
from mock import patch


@patch("backup_system_preferences.execute_shell")
def test_backup(execute_shell_mock):
execute_shell_mock.side_effect = execute_shell_func
backup_system_preferences.backup()


def execute_shell_func(*args):
arg_length = len(args)
assert arg_length > 0
command = args[0]
assert command[0] == "defaults"
assert command[1] == "export"
18 changes: 18 additions & 0 deletions test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import os
from os import path
import config


def test_get_backup_dir():
backup_dir = config.get_backup_dir()
assert backup_dir is not None

def test_get_backup_dir_works_with_environ():
os.environ['MACPREFS_BACKUP_DIR'] = "asdf"
backup_dir = config.get_backup_dir()
assert "asdf" in backup_dir


def test_get_backup_file_path():
assert config.get_backup_file_path("asdf.com") == path.join(
config.get_backup_dir(), "asdf.com.plist")
Loading

0 comments on commit a8a511e

Please sign in to comment.