9eba892764ca — Josiah Ulfers 10 years ago
Finally have this merge resolved
2 files changed, 298 insertions(+), 291 deletions(-)

M dss.py
M test.py
M dss.py +6 -0
@@ 15,6 15,8 @@ from docutils.core import publish_parts
 
 # TODO: ie http-equiv tag
 # TODO: generator meta tag?
+# TODO: header with home link
+# TODO: footer with mod date info
 default_template = string.Template('''<!DOCTYPE html>
 <html><head>
     $favicon

          
@@ 170,6 172,7 @@ class DeadSimpleSite(object):
                 shutil.copy2(source, target)
 
     def serve(self, port=8000):
+        SocketServer.TCPServer.allow_reuse_address = True
         server = SocketServer.TCPServer(('', port), LiveSiteHandler)
         server.site = self
         server.serve_forever()

          
@@ 189,6 192,7 @@ class DeadSimpleSite(object):
             if not origin:
                 raise GitError('Origin required to clone remote repository')
             try:
+                # TODO: if the github repo is not initialized, clone fails, so make a first commit
                 self._git('clone', origin, '.')
             except subprocess.CalledProcessError as e:
                 raise GitError(e)

          
@@ 196,6 200,7 @@ class DeadSimpleSite(object):
         try:
             self._git('--version')
         except subprocess.CalledProcessError as e:
+            # TODO: on nix, this raises OSError
             raise GitError('No git command found. Is git installed and on the path?')
         self._clean()
         if not path.exists(self.target):

          
@@ 221,6 226,7 @@ class DeadSimpleSite(object):
             self.compile()
             self._git('add', '-A')
             self._git('commit', '-m', 'Dead Simple Site auto publish')
+            # TODO: revert commit if push fails
             self._git('push', '-u', 'origin', 'gh-pages')
         except subprocess.CalledProcessError as e:
             raise GitError(e)

          
M test.py +292 -291
@@ 1,291 1,292 @@ 
-import os
-from os import path
-import re
-import shutil
-import subprocess
-import unittest
-
-import dss
-from dss import DeadSimpleSite
-
-def page_contain_assertion(testcase, filepath, invert=False):
-    with open(filepath) as f:
-        filetext = f.read()
-    def page_contains(pattern):
-        testcase.assertTrue(invert != bool(re.search(pattern, filetext, re.DOTALL)))
-    return page_contains
-
-def clean(directory):
-    if path.exists(directory):
-        shutil.rmtree(directory)
-
-class TestCompile(unittest.TestCase):
-
-    def setUp(self):
-        clean('test/_site')
-
-    def test_canary(self):
-        ''' Do nothing '''
-
-    def test_copy(self):
-        self.assertFalse(path.exists('test/_site'))
-        DeadSimpleSite('test').compile()
-        self.assertTrue(path.exists('test/_site'))
-        self.assertTrue(path.exists('test/_site/favicon.ico'))
-        self.assertTrue(path.exists('test/_site/subdir/plain.html'))
-
-    def test_render_default(self):
-        DeadSimpleSite('test').compile()
-        self.assertFalse(path.exists('test/_site/index.rst'))
-        page_contains = page_contain_assertion(self, 'test/_site/index.html')
-        # TODO: inline markup in title?
-        page_contains(r'<head>.*<title>Becomes Title</title>.*</head>')
-        page_contains(r'<body>.*Becomes content.*</body>')
-        page_contains(r'<link rel="stylesheet" href="style/global\.css">')
-        # TODO: html escape js paths
-        page_contains(r'<script src="js/first/foo\.js"></script>')
-        page_contains(r'<script src="js/bar\.js"></script>')
-        page_contains(r'<link rel="shortcut icon" href="favicon.ico">')
-
-    def test_render_default_subdir(self):
-        DeadSimpleSite('test').compile()
-        page_contains = page_contain_assertion(self, 'test/_site/subdir/page.html')
-        page_contains(r'<link rel="stylesheet" href="\.\./style/global\.css">')
-        # TODO: test for script alphabetical ordering
-        # Notice this tests script depth ordering:
-        page_contains(r'<script src="\.\./js/first/foo\.js"></script>.*<script src="\.\./js/bar\.js"></script>.*')
-        # TODO: rst includes
-        DeadSimpleSite('test').compile()
-        not_page_contains = page_contain_assertion(self, 'test/_site/subdir/page.html', invert=True)
-        # Tests that recompiling does not pick up scripts from the output directory
-        not_page_contains(r'<script src=.*_site')
-
-    def test_render_custom(self):
-        DeadSimpleSite('test').compile()
-        page_contains = page_contain_assertion(self, 'test/_site/custom.html')
-        page_contains(r'<link rel="stylesheet" href="style/global\.css">')
-        page_contains(r'<script src="js/first/foo\.js"></script>')
-        page_contains(r'<script src="js/bar\.js"></script>')
-        page_contains(r'Title From Content')
-        page_contains(r'ReStructured Text content')
-        page_contains(r'<p>Some html content</p>')
-
-    def test_cleanup(self):
-        ''' Compiling removes files and directories that do not exist in the source '''
-        os.makedirs('test/_site/empty')
-        open('test/_site/empty/bar', 'w').close()
-        DeadSimpleSite('test').compile()
-        self.assertFalse(path.exists('test/_site/empty/bar'))
-        self.assertFalse(path.exists('test/_site/empty'))
-
-    def test_ignore_dotfiles(self):
-        ''' Ignores any files that begin with "." except .htaccess '''
-        # TODO: worry about hidden files and such on windows?
-        os.makedirs('test/_site/.ignored-empty-target-folder')
-        os.makedirs('test/_site/.ignored-full-target-folder')
-        open('test/_site/.ignored-target-file', 'w').close()
-        open('test/_site/.ignored-full-target-folder/non-dotfile', 'w').close()
-        DeadSimpleSite('test').compile()
-        self.assertTrue(path.exists('test/_site/.htaccess'))
-        self.assertTrue(path.exists('test/_site/subdir/.htaccess'))
-        self.assertFalse(path.exists('test/_site/.ignored'))
-        self.assertFalse(path.exists('test/_site/.ignored-folder'))
-        self.assertTrue(path.exists('test/_site/.ignored-empty-target-folder'))
-        self.assertTrue(path.exists('test/_site/.ignored-target-file'))
-        self.assertTrue(path.exists('test/_site/.ignored-full-target-folder/non-dotfile'))
-        not_page_contains = page_contain_assertion(self, 'test/_site/index.html', invert=True)
-        not_page_contains(r'<script src=.*anything\.js')
-        not_page_contains(r'\.ignored\.js')
-
-class TestBarrenCompile(unittest.TestCase):
-
-    def setUp(self):
-        clean('test-barren/_site')
-
-    def test_render_barren(self):
-        DeadSimpleSite('test-barren').compile()
-        not_contains = page_contain_assertion(self, 'test-barren/_site/index.html', True)
-        not_contains('<link rel="stylesheet"')
-        not_contains('<script')
-        not_contains('favicon')
-
-    def test_cleans_htaccess(self):
-        os.makedirs('test-barren/_site')
-        open('test-barren/_site/.htaccess', 'w').close()
-        DeadSimpleSite('test-barren').compile()
-        self.assertFalse(path.exists('test-barren/_site/.htaccess'))
-
-class TestPublish(unittest.TestCase):
-
-    prep_commands = [
-        ('--version',),
-        ('rev-parse', '--git-dir')]
-    update_commands = [
-        ('checkout', 'gh-pages'),
-        ('pull',),
-        ('add', '-A'),
-        ('commit', '-m', 'Dead Simple Site auto publish'),
-        ('push', '-u', 'origin', 'gh-pages',)]
-    clone_commands = prep_commands \
-        + [('clone', 'https://example.com/user/repo.git', '.'), ('reset', '--hard')] \
-        + update_commands
-    
-    remote = 'https://example.com/user/repo.git'
-
-    def setUp(self):
-        self._real_git = DeadSimpleSite._git
-
-        self.fail_git = None
-        self.gitdir = '.git'
-        self.site = DeadSimpleSite('test')
-        self.git_invocations = []
-
-        clean('test/_site')
-        self.site.compile() # make sure the output dir does not start clean, to test cleanup
-
-        def git(site, *gitargs):
-            self.git_invocations.append(gitargs)
-            if (gitargs[0] == self.fail_git):
-                self.fail_git = None
-                raise subprocess.CalledProcessError('foo', 'bar', 'baz')
-            if (gitargs == ('rev-parse', '--git-dir')):
-                self.assertTrue(path.exists(self.site.target))
-                return self.gitdir
-            if (gitargs[0] == 'clone'):
-                self.assertTrue(gitargs[1] != None)
-            if (gitargs[0] == 'reset'):
-                self.assertFalse(path.exists('test/_site/index.html'))
-            if (gitargs[0] == 'add'):
-                self.assertTrue(path.exists('test/_site/index.html'))
-        DeadSimpleSite._git = git
-
-    def tearDown(self):
-        DeadSimpleSite._git = self._real_git
-
-    def test_creates_target(self):
-        clean('test/_site')
-        self.site.publish()
-
-    def test_no_git(self):
-        self.fail_git = '--version'
-        with self.assertRaisesRegexp(dss.GitError, 'No git command found'):
-            self.site.publish()
-
-    def test_arbitrary_git_error(self):
-        self.fail_git = 'reset'
-        with self.assertRaisesRegexp(dss.GitError, 'baz'):
-            self.site.publish()
-
-    def test_existing_repo(self):
-        self.site.publish()
-        self.assertEqual(self.prep_commands + [('reset', '--hard')] + self.update_commands,
-            self.git_invocations)
-
-    def test_clone_when_source_in_git(self):
-        self.gitdir = self.site.source + '/.git'
-        self.site.publish(self.remote)
-        self.assertEqual(self.clone_commands, self.git_invocations)
-
-    def test_clone_when_source_not_in_git(self):
-        self.fail_git = 'rev-parse'
-        self.site.publish(self.remote)
-        self.assertEqual(self.clone_commands, self.git_invocations)
-
-    def test_clone_error_no_origin(self):
-        self.gitdir = self.site.source + '/.git'
-        with self.assertRaisesRegexp(dss.GitError, 'Origin required to clone remote repository'):
-            self.site.publish()
-
-    def test_clone_error_wrapping(self):
-        self.gitdir = self.site.source + '/.git'
-        self.fail_git = 'clone'
-        with self.assertRaisesRegexp(dss.GitError, 'baz'):
-            self.site.publish(self.remote)
-
-    def test_no_gh_pages_branch(self):
-        self.fail_git = 'checkout'
-        self.site.publish()
-        self.assertEqual(self.prep_commands
-            + [ ('reset', '--hard'),
-                ('checkout', 'gh-pages'),
-                ('branch', 'gh-pages')]
-            + [c for c in self.update_commands if c != ('pull',)],
-            self.git_invocations)
-
-class TestTemplate(unittest.TestCase):
-
-    def setUp(self):
-        clean('test-templates/_site')
-
-    def test_template_nest(self):
-        DeadSimpleSite('test-templates').compile()
-        self.assertFalse(path.exists('test-templates/_site/__template__.html'))
-        self.assertFalse(path.exists('test-templates/_site/subdir/subsubdir/__template__.html'))
-        index_contains = page_contain_assertion(self, 'test-templates/_site/index.html')
-        index_contains(r'<a href="index\.html">Home</a>')
-        sub_contains = page_contain_assertion(self, 'test-templates/_site/subdir/root-template.html')
-        sub_contains(r'<a href="\.\./index\.html">Home</a>')
-        not_sub_sub_contains = page_contain_assertion(self,
-            'test-templates/_site/subdir/subsubdir/nested.html', invert=True)
-        not_sub_sub_contains(r'index\.html')
-
-class TestCli(unittest.TestCase):
-
-    def setUp(self):
-        self.compiled = None
-        self.served = None
-        self.served_on = None
-        self.origin = None
-
-        def dummy_compile(site):
-            self.compiled = site.source
-        
-        def dummy_serve(site, port):
-            self.served = site.source
-            self.served_on = port
-
-        def dummy_publish(site, origin):
-            self.origin = origin
-        
-        self._real_compile = DeadSimpleSite.compile
-        self._real_serve = DeadSimpleSite.serve
-        self._real_publish = DeadSimpleSite.publish
-        DeadSimpleSite.serve = dummy_serve
-        DeadSimpleSite.compile = dummy_compile
-        DeadSimpleSite.publish = dummy_publish
-
-    def tearDown(self):
-        DeadSimpleSite.serve = self._real_serve
-        DeadSimpleSite.compile = self._real_compile
-        DeadSimpleSite.publish = self._real_publish
-
-    def test_compile(self):
-        dss.cli(['compile'])
-        self.assertTrue(self.compiled == path.abspath('.'))
-    
-    def test_serve(self):
-        dss.cli(['serve'])
-        self.assertTrue(self.served == path.abspath('.'))
-        self.assertTrue(self.served_on == 8000)
-
-    def test_path_arg(self):
-        # TODO: SimpleHTTPRequestHandler still thinks its root is pwd
-        dss.cli(['--directory', 'test', 'serve'])
-        self.assertTrue(self.served == path.abspath('test'))
-        dss.cli(['-d', 'test', 'serve'])
-        self.assertTrue(self.served == path.abspath('test'))
-
-    def test_port_arg(self):
-        dss.cli(['serve', '-p', '8001'])
-        self.assertTrue(self.served_on == 8001)
-
-    def test_publish(self):
-        dss.cli(['publish'])
-        self.assertTrue(self.origin == None)
-        dss.cli(['publish', 'http://example.com/user/repo'])
-        self.assertTrue(self.origin == 'http://example.com/user/repo')
-
-    # TODO: tests for errors
-
-if __name__ == '__main__':
-    unittest.main()
+#!/usr/bin/env python
+import os
+from os import path
+import re
+import shutil
+import subprocess
+import unittest
+
+import dss
+from dss import DeadSimpleSite
+
+def page_contain_assertion(testcase, filepath, invert=False):
+    with open(filepath) as f:
+        filetext = f.read()
+    def page_contains(pattern):
+        testcase.assertTrue(invert != bool(re.search(pattern, filetext, re.DOTALL)))
+    return page_contains
+
+def clean(directory):
+    if path.exists(directory):
+        shutil.rmtree(directory)
+
+class TestCompile(unittest.TestCase):
+
+    def setUp(self):
+        clean('test/_site')
+
+    def test_canary(self):
+        ''' Do nothing '''
+
+    def test_copy(self):
+        self.assertFalse(path.exists('test/_site'))
+        DeadSimpleSite('test').compile()
+        self.assertTrue(path.exists('test/_site'))
+        self.assertTrue(path.exists('test/_site/favicon.ico'))
+        self.assertTrue(path.exists('test/_site/subdir/plain.html'))
+
+    def test_render_default(self):
+        DeadSimpleSite('test').compile()
+        self.assertFalse(path.exists('test/_site/index.rst'))
+        page_contains = page_contain_assertion(self, 'test/_site/index.html')
+        # TODO: inline markup in title?
+        page_contains(r'<head>.*<title>Becomes Title</title>.*</head>')
+        page_contains(r'<body>.*Becomes content.*</body>')
+        page_contains(r'<link rel="stylesheet" href="style/global\.css">')
+        # TODO: html escape js paths
+        page_contains(r'<script src="js/first/foo\.js"></script>')
+        page_contains(r'<script src="js/bar\.js"></script>')
+        page_contains(r'<link rel="shortcut icon" href="favicon.ico">')
+
+    def test_render_default_subdir(self):
+        DeadSimpleSite('test').compile()
+        page_contains = page_contain_assertion(self, 'test/_site/subdir/page.html')
+        page_contains(r'<link rel="stylesheet" href="\.\./style/global\.css">')
+        # TODO: test for script alphabetical ordering
+        # Notice this tests script depth ordering:
+        page_contains(r'<script src="\.\./js/first/foo\.js"></script>.*<script src="\.\./js/bar\.js"></script>.*')
+        # TODO: rst includes
+        DeadSimpleSite('test').compile()
+        not_page_contains = page_contain_assertion(self, 'test/_site/subdir/page.html', invert=True)
+        # Tests that recompiling does not pick up scripts from the output directory
+        not_page_contains(r'<script src=.*_site')
+
+    def test_render_custom(self):
+        DeadSimpleSite('test').compile()
+        page_contains = page_contain_assertion(self, 'test/_site/custom.html')
+        page_contains(r'<link rel="stylesheet" href="style/global\.css">')
+        page_contains(r'<script src="js/first/foo\.js"></script>')
+        page_contains(r'<script src="js/bar\.js"></script>')
+        page_contains(r'Title From Content')
+        page_contains(r'ReStructured Text content')
+        page_contains(r'<p>Some html content</p>')
+
+    def test_cleanup(self):
+        ''' Compiling removes files and directories that do not exist in the source '''
+        os.makedirs('test/_site/empty')
+        open('test/_site/empty/bar', 'w').close()
+        DeadSimpleSite('test').compile()
+        self.assertFalse(path.exists('test/_site/empty/bar'))
+        self.assertFalse(path.exists('test/_site/empty'))
+
+    def test_ignore_dotfiles(self):
+        ''' Ignores any files that begin with "." except .htaccess '''
+        # TODO: worry about hidden files and such on windows?
+        os.makedirs('test/_site/.ignored-empty-target-folder')
+        os.makedirs('test/_site/.ignored-full-target-folder')
+        open('test/_site/.ignored-target-file', 'w').close()
+        open('test/_site/.ignored-full-target-folder/non-dotfile', 'w').close()
+        DeadSimpleSite('test').compile()
+        self.assertTrue(path.exists('test/_site/.htaccess'))
+        self.assertTrue(path.exists('test/_site/subdir/.htaccess'))
+        self.assertFalse(path.exists('test/_site/.ignored'))
+        self.assertFalse(path.exists('test/_site/.ignored-folder'))
+        self.assertTrue(path.exists('test/_site/.ignored-empty-target-folder'))
+        self.assertTrue(path.exists('test/_site/.ignored-target-file'))
+        self.assertTrue(path.exists('test/_site/.ignored-full-target-folder/non-dotfile'))
+        not_page_contains = page_contain_assertion(self, 'test/_site/index.html', invert=True)
+        not_page_contains(r'<script src=.*anything\.js')
+        not_page_contains(r'\.ignored\.js')
+
+class TestBarrenCompile(unittest.TestCase):
+
+    def setUp(self):
+        clean('test-barren/_site')
+
+    def test_render_barren(self):
+        DeadSimpleSite('test-barren').compile()
+        not_contains = page_contain_assertion(self, 'test-barren/_site/index.html', True)
+        not_contains('<link rel="stylesheet"')
+        not_contains('<script')
+        not_contains('favicon')
+
+    def test_cleans_htaccess(self):
+        os.makedirs('test-barren/_site')
+        open('test-barren/_site/.htaccess', 'w').close()
+        DeadSimpleSite('test-barren').compile()
+        self.assertFalse(path.exists('test-barren/_site/.htaccess'))
+
+class TestPublish(unittest.TestCase):
+
+    prep_commands = [
+        ('--version',),
+        ('rev-parse', '--git-dir')]
+    update_commands = [
+        ('checkout', 'gh-pages'),
+        ('pull',),
+        ('add', '-A'),
+        ('commit', '-m', 'Dead Simple Site auto publish'),
+        ('push', '-u', 'origin', 'gh-pages',)]
+    clone_commands = prep_commands \
+        + [('clone', 'https://example.com/user/repo.git', '.'), ('reset', '--hard')] \
+        + update_commands
+    
+    remote = 'https://example.com/user/repo.git'
+
+    def setUp(self):
+        self._real_git = DeadSimpleSite._git
+
+        self.fail_git = None
+        self.gitdir = '.git'
+        self.site = DeadSimpleSite('test')
+        self.git_invocations = []
+
+        clean('test/_site')
+        self.site.compile() # make sure the output dir does not start clean, to test cleanup
+
+        def git(site, *gitargs):
+            self.git_invocations.append(gitargs)
+            if (gitargs[0] == self.fail_git):
+                self.fail_git = None
+                raise subprocess.CalledProcessError('foo', 'bar', 'baz')
+            if (gitargs == ('rev-parse', '--git-dir')):
+                self.assertTrue(path.exists(self.site.target))
+                return self.gitdir
+            if (gitargs[0] == 'clone'):
+                self.assertTrue(gitargs[1] != None)
+            if (gitargs[0] == 'reset'):
+                self.assertFalse(path.exists('test/_site/index.html'))
+            if (gitargs[0] == 'add'):
+                self.assertTrue(path.exists('test/_site/index.html'))
+        DeadSimpleSite._git = git
+
+    def tearDown(self):
+        DeadSimpleSite._git = self._real_git
+
+    def test_creates_target(self):
+        clean('test/_site')
+        self.site.publish()
+
+    def test_no_git(self):
+        self.fail_git = '--version'
+        with self.assertRaisesRegexp(dss.GitError, 'No git command found'):
+            self.site.publish()
+
+    def test_arbitrary_git_error(self):
+        self.fail_git = 'reset'
+        with self.assertRaisesRegexp(dss.GitError, 'baz'):
+            self.site.publish()
+
+    def test_existing_repo(self):
+        self.site.publish()
+        self.assertEqual(self.prep_commands + [('reset', '--hard')] + self.update_commands,
+            self.git_invocations)
+
+    def test_clone_when_source_in_git(self):
+        self.gitdir = self.site.source + '/.git'
+        self.site.publish(self.remote)
+        self.assertEqual(self.clone_commands, self.git_invocations)
+
+    def test_clone_when_source_not_in_git(self):
+        self.fail_git = 'rev-parse'
+        self.site.publish(self.remote)
+        self.assertEqual(self.clone_commands, self.git_invocations)
+
+    def test_clone_error_no_origin(self):
+        self.gitdir = self.site.source + '/.git'
+        with self.assertRaisesRegexp(dss.GitError, 'Origin required to clone remote repository'):
+            self.site.publish()
+
+    def test_clone_error_wrapping(self):
+        self.gitdir = self.site.source + '/.git'
+        self.fail_git = 'clone'
+        with self.assertRaisesRegexp(dss.GitError, 'baz'):
+            self.site.publish(self.remote)
+
+    def test_no_gh_pages_branch(self):
+        self.fail_git = 'checkout'
+        self.site.publish()
+        self.assertEqual(self.prep_commands
+            + [ ('reset', '--hard'),
+                ('checkout', 'gh-pages'),
+                ('branch', 'gh-pages')]
+            + [c for c in self.update_commands if c != ('pull',)],
+            self.git_invocations)
+
+class TestTemplate(unittest.TestCase):
+
+    def setUp(self):
+        clean('test-templates/_site')
+
+    def test_template_nest(self):
+        DeadSimpleSite('test-templates').compile()
+        self.assertFalse(path.exists('test-templates/_site/__template__.html'))
+        self.assertFalse(path.exists('test-templates/_site/subdir/subsubdir/__template__.html'))
+        index_contains = page_contain_assertion(self, 'test-templates/_site/index.html')
+        index_contains(r'<a href="index\.html">Home</a>')
+        sub_contains = page_contain_assertion(self, 'test-templates/_site/subdir/root-template.html')
+        sub_contains(r'<a href="\.\./index\.html">Home</a>')
+        not_sub_sub_contains = page_contain_assertion(self,
+            'test-templates/_site/subdir/subsubdir/nested.html', invert=True)
+        not_sub_sub_contains(r'index\.html')
+
+class TestCli(unittest.TestCase):
+
+    def setUp(self):
+        self.compiled = None
+        self.served = None
+        self.served_on = None
+        self.origin = None
+
+        def dummy_compile(site):
+            self.compiled = site.source
+        
+        def dummy_serve(site, port):
+            self.served = site.source
+            self.served_on = port
+
+        def dummy_publish(site, origin):
+            self.origin = origin
+        
+        self._real_compile = DeadSimpleSite.compile
+        self._real_serve = DeadSimpleSite.serve
+        self._real_publish = DeadSimpleSite.publish
+        DeadSimpleSite.serve = dummy_serve
+        DeadSimpleSite.compile = dummy_compile
+        DeadSimpleSite.publish = dummy_publish
+
+    def tearDown(self):
+        DeadSimpleSite.serve = self._real_serve
+        DeadSimpleSite.compile = self._real_compile
+        DeadSimpleSite.publish = self._real_publish
+
+    def test_compile(self):
+        dss.cli(['compile'])
+        self.assertTrue(self.compiled == path.abspath('.'))
+    
+    def test_serve(self):
+        dss.cli(['serve'])
+        self.assertTrue(self.served == path.abspath('.'))
+        self.assertTrue(self.served_on == 8000)
+
+    def test_path_arg(self):
+        # TODO: SimpleHTTPRequestHandler still thinks its root is pwd
+        dss.cli(['--directory', 'test', 'serve'])
+        self.assertTrue(self.served == path.abspath('test'))
+        dss.cli(['-d', 'test', 'serve'])
+        self.assertTrue(self.served == path.abspath('test'))
+
+    def test_port_arg(self):
+        dss.cli(['serve', '-p', '8001'])
+        self.assertTrue(self.served_on == 8001)
+
+    def test_publish(self):
+        dss.cli(['publish'])
+        self.assertTrue(self.origin == None)
+        dss.cli(['publish', 'http://example.com/user/repo'])
+        self.assertTrue(self.origin == 'http://example.com/user/repo')
+
+    # TODO: tests for errors
+
+if __name__ == '__main__':
+    unittest.main()