summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2019-06-16 14:57:55 +0300
committerLars Wirzenius <liw@liw.fi>2019-06-16 19:30:22 +0300
commit80858ea67d85800089d7842fc66505fa83103aa0 (patch)
tree2c2143d38eed9f3b17ca87b05cbf9b755ed84665
parentb28e50ac4cf013eb68f6d3a10b07f8683bd60122 (diff)
downloadfable-poc-80858ea67d85800089d7842fc66505fa83103aa0.tar.gz
Change: make generated test program more convenient
Also, the jt fable now works, against my locally modified version of jt (soon to be pushed to git.liw.fi). The generated program now has options --tempdir (default is something in /tmp). If --tempdir is used, it doesn't removed at the end. Each scenario gets a temporary directory created under the temporary directory. The generated program also has the options --log for specifying a log file, and --scenario to specify which scenarios to run (use once per scenario).
-rw-r--r--fable.py90
-rwxr-xr-xftt-codegen11
-rw-r--r--jt-prelude.py1
-rw-r--r--jt.md4
-rw-r--r--jt.py145
-rw-r--r--jt.yaml55
6 files changed, 288 insertions, 18 deletions
diff --git a/fable.py b/fable.py
index ec1d881..8e14357 100644
--- a/fable.py
+++ b/fable.py
@@ -1,7 +1,10 @@
# Note that the API of this module is guaranteed to change.
+import argparse
+import logging
import os
import sys
+import shutil
import tempfile
@@ -20,30 +23,111 @@ class Context:
self._vars[key] = value
+class Settingses:
+
+ def __init__(self):
+ self._scenarios = []
+
+ def add_scenario(self, name):
+ self._scenarios.append(name)
+
+ def is_wanted_scenario(self, name):
+ return (not self._scenarios or
+ any(name.casefold() == w.casefold() for w in self._scenarios))
+
+
+class Datadir:
+
+ def __init__(self):
+ self._dirname = None
+ self._ours = False
+
+ def get(self):
+ if self._dirname is None:
+ self._dirname = tempfile.mkdtemp()
+ logging.info('Created temporary directory %s', self._dirname)
+ self._ours = True
+ return self._dirname
+
+ def set(self, dirname):
+ self._dirname = os.path.abspath(dirname)
+ if not os.path.exists(self._dirname):
+ os.mkdir(self._dirname)
+ self._ours = False
+
+ def cleanup(self):
+ if self._ours:
+ logging.debug('Removing %s', self._dirname)
+ shutil.rmtree(self._dirname)
+
+
class Run:
def __init__(self):
self._name = None
self._context = None
self._srcdir = None
- self._tmpdir = None
+ self._settingses = Settingses()
+ self._datadir = None
+ self._tempdir = None
+ self._parse_options()
+
+ def _parse_options(self):
+ p = argparse.ArgumentParser()
+ p.add_argument('--log')
+ p.add_argument('--tempdir')
+ p.add_argument(
+ '--scenario', '-s', action='append', dest='scenarios', default=[])
+ args = p.parse_args()
+ self._setup_logging(args.log)
+ self._tempdir = args.tempdir
+ for s in args.scenarios:
+ self._settingses.add_scenario(s)
+
+ def _setup_logging(self, filename):
+ format = '%(asctime)s %(levelname)s %(message)s'
+ logging.basicConfig(
+ filename=filename, format=format, level=logging.DEBUG)
+ logging.info('test program starts =============')
def get_context(self):
return self._context
def start(self, name):
+ if not self._settingses.is_wanted_scenario(name):
+ logging.info('Skipping unwanted scenario %s', name)
+ return False
+
+ logging.info('Start scenario %s', name)
self._context = Context()
self._name = name
- self._tmpdir = tempfile.mkdtemp()
self._srcdir = os.getcwd()
- os.chdir(self._tmpdir)
+ self._datadir = Datadir()
+ self._datadir.set(self._tempdir)
+ dirname = tempfile.mkdtemp(dir=self._datadir.get())
+ os.chdir(dirname)
+ return True
def end(self):
+ logging.info('OK %s', self._name)
sys.stdout.write('OK: {}\n'.format(self._name))
os.chdir(self._srcdir)
+ self._datadir.cleanup()
+
+
+def assertTrue(a):
+ if not a:
+ raise Exception(
+ 'expected {!r} to be true, but was disappointed'.format(a, b))
def assertEqual(a, b):
if a != b:
raise Exception(
'expected {!r} == {!r}, but was disappointed'.format(a, b))
+
+
+def assertNotEqual(a, b):
+ if a == b:
+ raise Exception(
+ 'expected {!r} != {!r}, but was disappointed'.format(a, b))
diff --git a/ftt-codegen b/ftt-codegen
index c54a383..694cc05 100755
--- a/ftt-codegen
+++ b/ftt-codegen
@@ -118,8 +118,10 @@ def codegen(f, step, bindings):
keyword = words[0]
rest = ' '.join(words[1:])
function, args = find_binding(bindings, keyword, rest)
- f.write('args = {}\n'.format(json.dumps(args)))
- f.write('{}(run.get_context(), **args)\n'.format(function))
+ f.write(' logging.info("step %s", {})\n'.format(json.dumps(step)))
+ f.write(' args = {}\n'.format(json.dumps(args)))
+ f.write(' logging.debug("calling {} with args %s", args)\n'.format(function))
+ f.write(' {}(run.get_context(), **args)\n\n'.format(function))
def debug(msg):
if False:
@@ -134,6 +136,7 @@ debug('reading prelude')
prelude = open(sys.argv[2]).read()
sys.stdout.write(hardcoded)
sys.stdout.write(prelude)
+sys.stdout.write('\n')
debug('reading inputs')
text = ''.join(open(filename).read() for filename in sys.argv[3:])
@@ -154,9 +157,9 @@ for s in fable.get_scenarios():
f = sys.stdout
for s in scenarios:
name = s.get_name()
- f.write('run.start("{}")\n'.format(name))
+ f.write('\nif run.start("{}"):\n'.format(name))
for step in s.get_steps():
codegen(f, step, bindings)
- f.write('run.end()\n')
+ f.write(' run.end()\n')
debug('ok')
diff --git a/jt-prelude.py b/jt-prelude.py
new file mode 100644
index 0000000..be5c08f
--- /dev/null
+++ b/jt-prelude.py
@@ -0,0 +1 @@
+from jt import *
diff --git a/jt.md b/jt.md
index 3d68720..69dc534 100644
--- a/jt.md
+++ b/jt.md
@@ -109,7 +109,7 @@ and I run jt new "second entry"
then there are 2 drafts
and draft 0 has title "first entry"
and draft 1 has title "second entry"
-when I run jt finish
+when I try to run jt finish
then it fails
```
@@ -135,7 +135,7 @@ always requires specifying the draft.
given an empty journal
when I run jt new "random title"
then there is 1 draft
-when I run jt remove
+when I try to run jt remove
then it fails
when I run jt remove 0
then there are no drafts
diff --git a/jt.py b/jt.py
new file mode 100644
index 0000000..392af40
--- /dev/null
+++ b/jt.py
@@ -0,0 +1,145 @@
+import logging
+import os
+import subprocess
+import sys
+
+import fable
+
+def _jt_path():
+ return os.environ['JT_PATH']
+
+def _runcmd(ctx, argv):
+ logging.debug('Executing %r', argv)
+ p = subprocess.Popen(
+ argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+ out, err = p.communicate()
+ ctx["stdout"] = out
+ ctx["stderr"] = err
+ ctx["exit_code"] = p.returncode
+ logging.debug('Result: exit_code=%r', ctx['exit_code'])
+ if ctx['exit_code'] != 0:
+ logging.warning(
+ 'jt failed, stdout=%r stderr=%r', ctx['stdout'], ctx['stderr'])
+
+def create_empty_journal(ctx):
+ ctx['journal'] = os.path.abspath('journal')
+ ctx['blog'] = os.path.join(ctx['journal'], 'blog')
+ os.mkdir(ctx['journal'])
+
+ ctx['config'] = os.path.abspath('jt.conf')
+ with open(ctx['config'], 'w') as f:
+ f.write('[config]\n')
+ f.write('source = %s\n' % ctx['journal'])
+ f.write('git = no\n')
+ f.write('push = no\n')
+ f.write('notes-dir = blog\n')
+ f.write('editor = /bin/true {editor}\n')
+ f.write('log = %s.log\n' % ctx['journal'])
+ logging.debug('Created config file %s', ctx['config'])
+
+
+def run_jt(ctx, command=None):
+ try_jt(ctx, command=command)
+ fable.assertEqual(ctx['exit_code'], 0)
+
+def try_jt(ctx, command=None):
+ args = command.split()
+ assert args[0] == 'jt'
+
+ opts = [
+ '--no-default-config',
+ '--config', ctx['config'],
+ ]
+
+ argv = [_jt_path()] + opts + args[1:]
+ argv = ['/bin/sh', '-c', ' '.join(argv)]
+ _runcmd(ctx, argv)
+
+def create_empty_file(ctx, filename=None):
+ with open(filename, 'w') as f:
+ pass
+
+def jt_failed(ctx):
+ fable.assertNotEqual(ctx['exit_code'], 0)
+
+def _find_drafts(ctx):
+ draftsdir = os.path.join(ctx['journal'], 'drafts')
+ if not os.path.isdir(draftsdir):
+ return []
+ drafts = [f for f in _find_files(draftsdir) if f.endswith('.mdwn')]
+ logging.debug('drafts = %r', drafts)
+ return drafts
+
+def no_drafts(ctx):
+ drafts = _find_drafts(ctx)
+ fable.assertEqual(drafts, [])
+
+def one_draft(ctx):
+ drafts = _find_drafts(ctx)
+ fable.assertEqual(len(drafts), 1)
+
+def one_draft_with_attachment(ctx, filename=None):
+ drafts = _find_drafts(ctx)
+ fable.assertEqual(len(drafts), 1)
+ filename = drafts[0]
+ fable.assertTrue(filename.endswith('.mdwn'))
+ dirname = filename[:-len('.mdwn')]
+ fable.assertTrue(os.path.isdir(dirname))
+ attachment = os.path.join(dirname, filename)
+ fable.assertTrue(os.path.exists(attachment))
+
+def n_drafts(ctx, count=None):
+ n = int(count)
+ drafts = _find_drafts(ctx)
+ fable.assertEqual(len(drafts), n)
+
+def _get_draft(ctx, id):
+ filename = os.path.join(ctx['journal'], 'drafts', '%s.mdwn' % id)
+ return _cat(filename)
+
+def draft_has_title(ctx, id=None, title=None):
+ draft = _get_draft(ctx, id)
+ fable.assertTrue(title in draft)
+
+def _find_files(dirname):
+ for d, s, f in os.walk(dirname):
+ for filename in f:
+ yield os.path.join(d, filename)
+
+def _find_entries(ctx):
+ blogdir = ctx['blog']
+ logging.debug('blogdir: %r', blogdir)
+ if not os.path.isdir(blogdir):
+ return []
+ entries = [f for f in _find_files(blogdir) if f.endswith('.mdwn')]
+ logging.debug('entries = %r', entries)
+ return entries
+
+def one_entry(ctx):
+ entries = _find_entries(ctx)
+ fable.assertEqual(len(entries), 1)
+
+def n_entries(ctx, count=None):
+ n = int(count)
+ entries = _find_entries(ctx)
+ fable.assertEqual(len(entries), n)
+
+def no_entries(ctx):
+ entries = _find_entries(ctx)
+ fable.assertEqual(len(entries), 0)
+
+def _find_topics(ctx):
+ dirname = os.path.join(ctx['journal'], 'topics')
+ filenames = [f for f in _find_files(dirname) if f.endswith('.mdwn')]
+ logging.debug('topics: %r', filenames)
+ return filenames
+
+def _cat(filename):
+ with open(filename) as f:
+ return f.read()
+
+def one_topic(ctx, topic=None, title=None):
+ topics = _find_topics(ctx)
+ fable.assertEqual(len(topics), 1)
+ topic = _cat(topics[0])
+ fable.assertTrue(title in topic)
diff --git a/jt.yaml b/jt.yaml
index f33cd94..22a5c90 100644
--- a/jt.yaml
+++ b/jt.yaml
@@ -1,10 +1,47 @@
-- given: a file (?P<filename>.+)
-- when: I run (?P<cmd>jt .*)
-- then: there is (?P<count>\d+) draft
-- then: there is (?P<count>\d+) draft, with attachment (?P<filename>.+)
-- then: there is (?P<count>\d+) journal entry
-- then: there is (?P<count>\d+) journal entry, with attachment (?P<filename>.+)
-- then: there is (?P<count>\d+) journal entry, belonging to (?P<id>.+)
-- then: there is (?P<count>\d+) topic, (?P<id>.+), titled "(?P<title>.+)"
+- given: a file (?P<filename>\S+)
+ function: create_empty_file
+
+- given: an empty journal
+ function: create_empty_journal
+
+- then: draft (?P<id>\d+) has title "(?P<title>.+)"
+ function: draft_has_title
+
+- then: it fails
+ function: jt_failed
+
+- then: there are (?P<count>\d+) drafts
+ function: n_drafts
+
- then: there are (?P<count>\d+) journal entries
-- then: draft (?P<id>\d+) has title "(?P<title>.*)"
+ function: n_entries
+
+- then: there are no drafts
+ function: no_drafts
+
+- then: there are no journal entries
+ function: no_entries
+
+- then: there is 1 draft
+ function: one_draft
+
+- then: there is 1 draft, with attachment (?P<filename>\S+)
+ function: one_draft_with_attachment
+
+- then: there is 1 journal entry
+ function: one_entry
+
+- then: there is 1 journal entry, belonging to (?P<topic>\S+)
+ function: one_entry_on_topic
+
+- then: there is 1 journal entry, with attachment (?P<filename>\S+)
+ function: one_enry_with_attachment
+
+- then: there there is 1 topic, (?P<topic>\S+), titled "(?P<title>.+)"
+ function: one_topic
+
+- when: I run (?P<command>jt .*)
+ function: run_jt
+
+- when: I try to run (?P<command>jt .*)
+ function: try_jt