pax_global_header00006660000000000000000000000064123330615770014521gustar00rootroot0000000000000052 comment=6d96dff0c56aafa87dcbf450aa2eabacf1720236 assword-0.8/000077500000000000000000000000001233306157700130525ustar00rootroot00000000000000assword-0.8/.gitignore000066400000000000000000000000311233306157700150340ustar00rootroot00000000000000*~ *.pyc build assword.1 assword-0.8/COPYING000066400000000000000000000012141233306157700141030ustar00rootroot00000000000000Assword is free software. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program, (in the COPYING-GPL-3 file in this directory). If not, see http://www.gnu.org/licenses/ assword-0.8/Makefile000077500000000000000000000013541233306157700145200ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- VERSION:=$(shell git describe --tags | sed -e s/_/~/ -e s/-/+/ -e s/-/~/ -e 's|.*/||') .PHONY: all all: assword.1 .PHONY: test test: ./test/assword-test $(TEST_OPTS) assword.1: assword help2man ./assword -N -n 'Simple and secure password database and retrieval system' \ --include=assword.1.additional \ -o $@ .PHONY: clean clean: rm -f assword.1 .PHONY: debian-snapshot debian-snapshot: rm -rf build/deb mkdir -p build/deb/debian git archive HEAD | tar -x -C build/deb/ git archive debian:debian | tar -x -C build/deb/debian/ cd build/deb; dch -b -v $(VERSION) -D UNRELEASED 'test build, not for upload' cd build/deb; echo '3.0 (native)' > debian/source/format cd build/deb; debuild -us -uc assword-0.8/README000066400000000000000000000045651233306157700137440ustar00rootroot00000000000000Assword - simple and secure password management system ====================================================== Assword is a secure password manager that relies on your OpenPGP key for security and is designed to integrate in a minimal fashion into any X11 window manager. Passwords and context strings are stored in a single OpenPGP-encrypted and signed file (meaning entry contexts are not exposed to the filesystem). Along with a simple command-line interface, there is a streamlined GUI meant for X11 window manager integration. When invoked, the GUI produces a prompt to search stored contexts. New entries can also easily be created. Passwords are securely retrieved without displaying on the screen. Multiple retrieval methods are available, including auto-typing them directly into an X11 window (default), or inserting them into the X11 clipboard. Contact ======= Assword was written by: Jameson Graef Rollins Daniel Kahn Gillmor Assword has a mailing list: assword@lists.mayfirst.org https://lists.mayfirst.org/mailman/listinfo/assword We also hang out on IRC: channel: #assword server: irc.oftc.net Getting Assword =============== Source ------ Clone the repo: $ git clone git://finestructure.net/assword $ cd assword Dependencies : * python (>= 2.6) * python-gpgme - Python wrapper for the GPGME library * python-gtk2 - Python bindings for the GTK+ widget set * python-pkg-resources - Package Discovery and Resource Access Recommends (for curses UI) : * python-xdo - Support for simulating X11 input (libxdo bindings) * xclip - Support for accessing X11 clipboard Debian ------ Assword is now available in Debian: http://packages.qa.debian.org/assword Debian/Ubuntu snapshot packages can also be easily made from the git source. You can build the package from any branch but it requires an up-to-date local branch of origin/debian, e.g.: $ git branch debian origin/debian Then: $ sudo apt-get install build-essential devscripts pkg-config python-all-dev python-setuptools debhelper dpkg-dev fakeroot $ make debian-snapshot $ sudo apt-get install python-gpgme python-gtk2 python-pkg-resources python-xdo xclip $ sudo dpkg -i build/assword_0.*_amd64.deb Using Assword ============= See the included assword(1) man page or built-in help string for detailed usage. assword-0.8/TODO000066400000000000000000000066371233306157700135560ustar00rootroot00000000000000 * BUG: assword crashes when it gets EOF on stdin when prompting or when ASSWORD_PASSWORD=prompt. * ENHANCEMENT: ASSWORD_PASSWORD=stdin should just read the password from the first line of stdin (discarding trailing newlines). not sure how this should interact with the situation where no context is supplied. * ENHANCEMENT: xdo has two input methods -- one based on XSendEvent, and one based on injecting keystrokes into the X session as a whole (the difference is whether a window ID parameter is passed to "type"). We use the latter right now in xsearch despite its possibility of leakage because some X11 applications ignore XSendEvent input. Provide some hinting mechanism derived from the window in question to conditionally enable XSendEvent instead. * ENHANCEMENT: be able to edit a context once it is created. for example, i often create a password when setting up an account on a remote web site. Some account signup workflows don't tell me if the account name i wanted was taken until after i've signed up. By then, i've created the password, and i want to adjust it manually. * ENHANCEMENT: can we use python clipboard bindings instead of "xclip -i"? * ENHANCEMENT: on xsearch if selected window is known browser, and it's possible to extract url, preseed context search with hostname of url. * ENHANCEMENT: xsearch create action should modify the database, move the user's focus back into the textentry (highlighting the whole entry text), disable the "Create" button, and *not* explicitly select and terminate. The user can then select in the usual way (pressing enter) or can continue without selecting. * ENHANCEMENT: review the label texts and make sure they're saying reasonable things in different parts of the workflow. * ENHANCEMENT: ctrl+del from xsearch when a matching context is present should allow deletion of the indicated password. This should probably prompt for confirmation. * ENHANCEMENT: ctrl+e from xsearch when a matching context is present should display another entry with the context's password in it; the user can then edit the password to adjust for stupid web site rules. the rest of the UI should be disabled (set_sensitive(False)) while this password editor is active. * ENHANCEMENT: consider how to deal with multiple DB backends, and post-save and pre-open hooks (e.g. to push to and fetch from a remote repository of these changes) * ENHANCEMENT: associate default _XPASTE mechanisms with some contexts: if you know that certain passwords work with tools that prefer certain _XPASTE mechanisms, that ought to be something assword can figure out. see 526990F5.6050700@guerrier.com and following discussion. * ENHANCEMENT: import scripts from common password stores (e.g. ~/.mozilla/firefox/*.default/signons.sqlite) * ENHANCEMENT: guess about target window to determine default _XPASTE mechanism (e.g. we know iceweasel works with one _XPASTE mechanism, but rxvt works with another one) -- we can guess by looking at the process that controls the window and/or the window title or other things (we might need to expand python-xdo to get these guesses) * ENHANCEMENT: use the target window title and/or pid to pre-seed the search box in "assword gui" (this should be pre-selected text so it is easy to start typing something else) * ENHANCEMENT: test for various PASSWORD values assword-0.8/assword000077500000000000000000000256611233306157700144740ustar00rootroot00000000000000#!/usr/bin/env python import os import sys import getpass import json import gpgme import assword import subprocess import pkg_resources ############################################################ PROG = os.path.basename(sys.argv[0]) def version(): print PROG, pkg_resources.get_distribution('assword').version def usage(): print "Usage:", PROG, " [...]" print """ The password database is stored as a single json object, OpenPGP encrypted and signed, and written to local disk (see ASSWORD_DB). The file will be created upon addition of the first entry. Database entries are keyed by 'context'. During retrieval of passwords, the database is decrypted and read into memory. Contexts are search by sub-string match. Commands: add [] Add a new entry. If context is '-' read from stdin. If not specified, user will be prompted for context. If the context already exists, an error will be thrown. See ASSWORD_PASSWORD for information on passwords. replace [] Replace password for existing entry. If context is '-' read from stdin. If not specified, user will be prompted for context. If the context does not exist an error will be thrown. See ASSWORD_PASSWORD for information on passwords. dump [] Dump search results as json. If string not specified all entries are returned. Passwords will not be displayed unless ASSWORD_DUMP_PASSWORDS is set. gui [] GUI interface, good for X11 window manager integration. Upon invocation the user will be prompted to decrypt the database, after which a graphical search prompt will be presented. If an additional string is provided, it will be added as the initial search string. All matching results for the query will be presented to the user. When a result is selected, the password will be retrieved according to the method specified by ASSWORD_XPASTE. If no match is found, the user has the opportunity to generate and store a new password, which is then delivered via ASSWORD_XPASTE. remove Delete an entry from the database. version Report the version of this program. help This help. Environment: ASSWORD_DB Path to assword database file. Default: ~/.assword/db ASSWORD_KEYFILE File containing OpenPGP key ID of database encryption recipient. Default: ~/.assword/keyid ASSWORD_KEYID OpenPGP key ID of database encryption recipient. This overrides ASSWORD_KEYFILE if set. ASSWORD_PASSWORD For new entries, entropy of auto-generated password in bytes (actual generated password will be longer due to base64 encoding). If set to 'prompt' user will be prompted for for password. Default: %d ASSWORD_DUMP_PASSWORDS Include passwords in dump when set. ASSWORD_XPASTE Method for password retrieval. Options are: 'xdo', which attempts to type the password into the window that had focus on launch, or 'xclip' which inserts the password in the X clipboard. Default: xdo """%(assword.DEFAULT_NEW_PASSWORD_OCTETS) ############################################################ ASSWORD_DIR = os.path.join(os.path.expanduser('~'),'.assword') DBPATH = os.getenv('ASSWORD_DB', os.path.join(ASSWORD_DIR, 'db')) ############################################################ def xclip(text): p = subprocess.Popen(' '.join(["xclip", "-i"]), shell=True, stdin=subprocess.PIPE) p.communicate(text) ############################################################ # Return codes: # 1 command/load line error # 10 db error # 20 gpg/key error ############################################################ def open_db(keyid=None): try: db = assword.Database(DBPATH, keyid) except assword.DatabaseError as e: print >>sys.stderr, 'Assword database error: %s' % e.msg sys.exit(10) if db.sigvalid is False: print >>sys.stderr, "WARNING: could not validate OpenPGP signature on db file." return db def get_keyid(): keyid = os.getenv('ASSWORD_KEYID') keyfile = os.getenv('ASSWORD_KEYFILE', os.path.join(ASSWORD_DIR, 'keyid')) if not keyid and os.path.exists(keyfile): with open(keyfile, 'r') as f: keyid = f.read().strip() save = False if not keyid: print >>sys.stderr, "OpenPGP key ID of encryption target not specified." print >>sys.stderr, "Please provide key ID in ASSWORD_KEYID environment variable," print >>sys.stderr, "or specify key ID now to save in ~/.assword/keyid file." keyid = raw_input('OpenPGP key ID: ') if keyid == '': keyid = None else: save = True if not keyid: sys.exit(20) try: gpg = gpgme.Context() gpg.get_key(keyid) except gpgme.GpgmeError: print >>sys.stderr, "Invalid key ID:", keyid sys.exit(20) if save: if not os.path.isdir(os.path.dirname(keyfile)): os.mkdir(os.path.dirname(keyfile)) with open(keyfile, 'w') as f: f.write(keyid) return keyid def retrieve_context(args): try: # get context as argument context = args[0] # or from stdin if context == '-': context = sys.stdin.read() # prompt for context if not specified except IndexError: try: context = raw_input('context: ') except KeyboardInterrupt: sys.exit(-1) if context == '': sys.exit("Can not add empty string context.") return context def retrieve_password(): # get password from prompt if requested if os.getenv('ASSWORD_PASSWORD') is None: return None elif os.getenv('ASSWORD_PASSWORD') != 'prompt': try: octets = int(os.getenv('ASSWORD_PASSWORD')) except ValueError: sys.exit("ASSWORD_PASSWORD environment variable is neither int or 'prompt'.") print >>sys.stderr, "Auto-generating password..." return octets try: password0 = getpass.getpass('password: ') password1 = getpass.getpass('reenter password: ') if password0 != password1: sys.exit("Passwords do not match. Aborting.") return password0 except KeyboardInterrupt: sys.exit(-1) ############################################################ # command functions # Add a password to the database. # First argument is potentially a context. def add(args): keyid = get_keyid() context = retrieve_context(args) db = open_db(keyid) if context in db: print >>sys.stderr, "Entry already exists with context: '%s'" % (context) sys.exit(1) password = retrieve_password() try: db.add(context.strip(), password) db.save() except assword.DatabaseError as e: print >>sys.stderr, 'Assword database error: %s' % e.msg sys.exit(10) print >>sys.stderr, "New entry writen." # Replace a password in the database. # First argument is context to replace. def replace(args): keyid = get_keyid() context = retrieve_context(args) db = open_db(keyid) if context not in db: print >>sys.stderr, "Context not found: '%s'" % (context) sys.exit(1) password = retrieve_password() try: db.replace(context.strip(), password) db.save() except assword.DatabaseError as e: print >>sys.stderr, 'Assword database error: %s' % e.msg sys.exit(10) print >>sys.stderr, "New entry writen." def dump(args): query = ' '.join(args) if not os.path.exists(DBPATH): print >>sys.stderr, """Assword database does not exist. To add an entry to the database use 'assword add'. See 'assword help' for more information.""" sys.exit(10) db = open_db() results = db.search(query) output = {} for context in results: output[context] = {} output[context]['date'] = results[context]['date'] if os.getenv('ASSWORD_DUMP_PASSWORDS'): output[context]['password'] = results[context]['password'] print json.dumps(output, sort_keys=True, indent=2) # The X GUI def gui(args, method='xdo'): query = ' '.join(args) if method == 'xdo': try: import xdo except: print >>sys.stderr, "The xdo module is not found, so the 'xdo' paste method is not available." print >>sys.stderr, "Please install python-xdo." sys.exit(1) # initialize xdo x = xdo.xdo() # get the id of the currently focused window win = x.get_focused_window() elif method == 'xclip': pass else: print >>sys.stderr, "Unknown X paste method:", method sys.exit(1) # do it keyid = get_keyid() db = open_db(keyid) result = assword.Gui(db, query=query).returnValue() # type the password in the saved window if result: if method == 'xdo': x.focus_window(win) x.wait_for_window_focus(win) x.type(result['password']) elif method == 'xclip': xclip(result['password']) def remove(args): keyid = get_keyid() try: context = args[0] except IndexError: print >>sys.stderr, "Must specify index to remove." sys.exit(1) db = open_db(keyid) if context not in db: print >>sys.stderr, "No entry with context: '%s'" % (context) sys.exit(1) try: print >>sys.stderr, "Really remove entry '%s'?" % (context) response = raw_input("Type 'yes' to remove: ") except KeyboardInterrupt: sys.exit(-1) if response != 'yes': sys.exit(-1) try: db.remove(context) db.save() except assword.DatabaseError as e: print >>sys.stderr, 'Assword database error: %s' % e.msg sys.exit(10) print >>sys.stderr, "Entry removed." ############################################################ # main if len(sys.argv) < 2: print >>sys.stderr, "Command not specified." usage() sys.exit(1) cmd = sys.argv[1] if cmd == 'add': add(sys.argv[2:]) elif cmd == 'replace': replace(sys.argv[2:]) elif cmd == 'dump': dump(sys.argv[2:]) elif cmd == 'gui': method = os.getenv('ASSWORD_XPASTE', 'xdo') gui(sys.argv[2:], method=method) elif cmd == 'remove': remove(sys.argv[2:]) elif cmd == 'version' or cmd == '--version': version() elif cmd == 'help' or cmd == '--help': usage() else: print >>sys.stderr, "Unknown command:", cmd print >>sys.stderr usage() sys.exit(1) assword-0.8/assword.1.additional000066400000000000000000000002771233306157700167330ustar00rootroot00000000000000[Signature validation] During decryption, OpenPGP signatures on the db file are checked for validity. If any of them are found to not be valid, a warning message will be written to stderr. assword-0.8/assword.py000066400000000000000000000273361233306157700151210ustar00rootroot00000000000000import os import io import gpgme import json import time import base64 import datetime import pygtk pygtk.require('2.0') import gtk import gobject ############################################################ DEFAULT_NEW_PASSWORD_OCTETS=18 def pwgen(bytes): """Return *bytes* bytes of random data, base64-encoded.""" s = os.urandom(bytes) return base64.b64encode(s) ############################################################ class DatabaseError(Exception): def __init__(self, msg): self.msg = msg def __str__(self): return repr(self.msg) class Database(): """An Assword database.""" def __init__(self, dbpath=None, keyid=None): """Database at dbpath will be decrypted and loaded into memory. If dbpath not specified, empty database will be initialized. The sigvalid property is set False if any OpenPGP signatures on the db file are invalid. sigvalid is None for new databases. """ self._dbpath = dbpath self._keyid = keyid # default database information self._type = 'assword' self._version = 1 self._entries = {} self._gpg = gpgme.Context() self._gpg.armor = True self._sigvalid = None if self._dbpath and os.path.exists(self._dbpath): try: cleardata = self._decryptDB(self._dbpath) # FIXME: trap exception if json corrupt jsondata = json.loads(cleardata.getvalue()) except IOError as e: raise DatabaseError(e) except gpgme.GpgmeError as e: raise DatabaseError('Decryption error: %s' % (e[2])) # unpack the json data if 'type' not in jsondata or jsondata['type'] != self._type: raise DatabaseError('Database is not a proper assword database.') if 'version' not in jsondata or jsondata['version'] != self._version: raise DatabaseError('Incompatible database.') self._entries = jsondata['entries'] @property def version(self): """Database version.""" return self._version @property def sigvalid(self): """Validity of OpenPGP signature on db file.""" return self._sigvalid def __str__(self): return '' % (self._dbpath) def __repr__(self): return 'assword.Database("%s")' % (self._dbpath) def __getitem__(self, context): """Return database entry for exact context.""" return self._entries[context] def __contains__(self, context): """True if context string in database.""" return context in self._entries def __iter__(self): """Iterator of all database contexts.""" return iter(self._entries) def _decryptDB(self, path): data = io.BytesIO() with io.BytesIO() as encdata: with open(path, 'rb') as f: encdata.write(f.read()) encdata.seek(0) sigs = self._gpg.decrypt_verify(encdata, data) # check signature if not sigs[0].validity >= gpgme.VALIDITY_FULL: self._sigvalid = False else: self._sigvalid = True data.seek(0) return data def _encryptDB(self, data, keyid): # The signer and the recipient are assumed to be the same. # FIXME: should these be separated? try: recipient = self._gpg.get_key(keyid or self._keyid) signer = self._gpg.get_key(keyid or self._keyid) except: raise DatabaseError('Could not retrieve GPG encryption key.') flags = gpgme.ENCRYPT_ALWAYS_TRUST try: flags |= gpgme.ENCRYPT_NO_COMPRESS except AttributeError: pass self._gpg.signers = [signer] encdata = io.BytesIO() data.seek(0) sigs = self._gpg.encrypt_sign([recipient], flags, data, encdata) encdata.seek(0) return encdata def _set_entry(self, context, password=None): if not isinstance(password, str): if password is None: bytes = DEFAULT_NEW_PASSWORD_OCTETS if isinstance(password, int): bytes = password password = pwgen(bytes) e = {'password': password, 'date': datetime.datetime.now().isoformat()} self._entries[context] = e return e def add(self, context, password=None): """Add a new entry to the database. If password is None, one will be generated automatically. If password is an int it will be interpreted as the number of random bytes to use. If the context is already in the db a DatabaseError will be raised. Database changes are not saved to disk until the save() method is called. """ if context == '': raise DatabaseError("Can not add empty string context") if context in self: raise DatabaseError("Context already exists (see replace())") return self._set_entry(context, password) def replace(self, context, password=None): """Replace entry in database. If password is None, one will be generated automatically. If password is an int it will be interpreted as the number of random bytes to use. If the context is not in the db a DatabaseError will be raised. Database changes are not saved to disk until the save() method is called. """ if context not in self: raise DatabaseError("Context not found (see add())") return self._set_entry(context, password) def remove(self, context): """Remove an entry from the database. Database changes are not saved to disk until the save() method is called. """ del self._entries[context] def save(self, keyid=None, path=None): """Save database to disk. Key ID must either be specified here or at database initialization. If path not specified, database will be saved at original dbpath location. """ # FIXME: should check that recipient is not different than who # the db was originally encrypted for if not keyid: keyid = self._keyid if not keyid: raise DatabaseError('Key ID for decryption not specified.') if not path: path = self._dbpath if not path: raise DatabaseError('Save path not specified.') jsondata = {'type': self._type, 'version': self._version, 'entries': self._entries} cleardata = io.BytesIO(json.dumps(jsondata, indent=2)) encdata = self._encryptDB(cleardata, keyid) newpath = path + '.new' bakpath = path + '.bak' with open(newpath, 'w') as f: f.write(encdata.getvalue()) if os.path.exists(path): os.rename(path, bakpath) os.rename(newpath, path) def search(self, query=None): """Search for query in contexts. If query is None, all entries will be returned. """ mset = {} for context, entry in self._entries.iteritems(): # simple substring match if query in context: mset[context] = entry return mset ############################################################ # Assumes that the func_data is set to the number of the text column in the # model. def _match_func(completion, key, iter, column): model = completion.get_model() text = model[iter][column] if text.lower().find(key.lower()) > -1: return True return False class Gui: """Assword X-based query UI.""" def __init__(self, db, query=None): self.db = db self.query = None self.results = None self.selected = None self.window = None self.entry = None self.label = None if query: # If we have an intial query, directly do a search without # initializing any X objects. This will initialize the # database and potentially return entries. r = self.db.search(query) # If only a single entry is found, _search() will set the # result and attempt to close any X objects (of which # there are none). Since we don't need to initialize any # GUI, return the initialization immediately. # See .returnValue(). if len(r) == 1: self.selected = r[r.keys()[0]] return self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_border_width(4) windowicon = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_DIALOG) self.window.set_icon(windowicon) self.entry = gtk.Entry() if query: self.entry.set_text(query) completion = gtk.EntryCompletion() self.entry.set_completion(completion) liststore = gtk.ListStore(gobject.TYPE_STRING) completion.set_model(liststore) completion.set_text_column(0) completion.set_match_func(_match_func, 0) # 0 is column number context_len = 20 for context in self.db: if len(context) > context_len: context_len = len(context) liststore.append([context]) hbox = gtk.HBox() vbox = gtk.VBox() self.createbutton = gtk.Button("Create") self.label = gtk.Label("enter context for desired password:") self.window.add(vbox) if self.db.sigvalid is False: notification = gtk.Label() msg = "WARNING: could not validate signature on db file" notification.set_markup('%s' % msg) if len(msg) > context_len: context_len = len(msg) hsep = gtk.HSeparator() vbox.add(notification) vbox.add(hsep) notification.show() hsep.show() vbox.add(self.label) vbox.pack_end(hbox, False, False) hbox.add(self.entry) hbox.pack_end(self.createbutton, False, False) self.entry.set_width_chars(context_len) self.entry.connect("activate", self.enter) self.entry.connect("changed", self.updatecreate) self.createbutton.connect("clicked", self.create) self.window.connect("destroy", self.destroy) self.window.connect("key-press-event", self.keypress) self.entry.show() self.label.show() vbox.show() hbox.show() self.createbutton.show() self.updatecreate(self.entry) self.window.show() def keypress(self, widget, event): if event.keyval == gtk.keysyms.Escape: gtk.main_quit() def updatecreate(self, widget, data=None): e = self.entry.get_text() self.createbutton.set_sensitive(e != '' and e not in self.db) def enter(self, widget, data=None): e = self.entry.get_text() if e in self.db: self.selected = self.db[e] if self.selected is None: self.label.set_text("weird -- no context found even though we thought there should be one") else: gtk.main_quit() else: self.label.set_text("no match") def create(self, widget, data=None): e = self.entry.get_text() self.selected = self.db.add(e) self.db.save() gtk.main_quit() def destroy(self, widget, data=None): gtk.main_quit() def returnValue(self): if self.selected is None: gtk.main() return self.selected assword-0.8/setup.py000077500000000000000000000007721233306157700145750ustar00rootroot00000000000000#!/usr/bin/env python from distutils.core import setup setup( name = 'assword', version = '0.8', description = 'Secure password management and retrieval system.', author = 'Jameson Rollins', author_email = 'jrollins@finestructure.net', url = 'http://finestructure.net/assword', py_modules = ['assword'], scripts = ['assword'], requires = [ 'gpgme', 'getpass', 'json', 'base64', 'gtk2', 'pkg_resources', ], ) assword-0.8/test/000077500000000000000000000000001233306157700140315ustar00rootroot00000000000000assword-0.8/test/assword-test000077500000000000000000000017601233306157700164220ustar00rootroot00000000000000#!/usr/bin/env bash # Run tests # # Copyright (c) 2005 Junio C Hamano # # Adapted from a Makefile to a shell script by Carl Worth (2010) if [ ${BASH_VERSINFO[0]} -lt 4 ]; then echo "Error: The assword test suite requires a bash version >= 4.0" echo "due to use of associative arrays within the test suite." echo "Please try again with a newer bash (or help us fix the" echo "test suite to be more portable). Thanks." exit 1 fi cd $(dirname "$0") TESTS=" basic library cli " # setup TESTS=${ASSWORD_TESTS:=$TESTS} # test for timeout utility if command -v timeout >/dev/null; then TEST_TIMEOUT_CMD="timeout 1m " echo "INFO: using 1 minute timeout for tests" else TEST_TIMEOUT_CMD="" fi # Prep rm -rf test-results trap 'e=$?; kill $!; exit $e' HUP INT TERM # Run the tests for test in $TESTS; do $TEST_TIMEOUT_CMD ./$test "$@" & wait $! done trap - HUP INT TERM # Report results ./lib/test-aggregate-results test-results/* # Clean up rm -rf test-results assword-0.8/test/basic000077500000000000000000000043771233306157700150530ustar00rootroot00000000000000#!/usr/bin/env bash # # Copyright (c) 2005 Junio C Hamano # test_description='the test framework itself' . lib/test-lib.sh ################################################################ test_expect_success 'success is reported like this' ' : ' test_set_prereq HAVEIT haveit=no test_expect_success HAVEIT 'test runs if prerequisite is satisfied' ' test_have_prereq HAVEIT && haveit=yes ' clean=no test_expect_success 'tests clean up after themselves' ' test_when_finished clean=yes ' cleaner=no test_expect_code 1 'tests clean up even after a failure' ' test_when_finished cleaner=yes && (exit 1) ' if test $clean$cleaner != yesyes then say "bug in test framework: cleanup commands do not work reliably" exit 1 fi test_expect_code 2 'failure to clean up causes the test to fail' ' test_when_finished "(exit 2)" ' # Ensure that all tests are being run test_begin_subtest 'Ensure that all available tests will be run' eval $(sed -n -e '/^TESTS="$/,/^"$/p' $TEST_DIRECTORY/assword-test) tests_in_suite=$(for i in $TESTS $TESTS_NET; do echo $i; done | sort) available=$(find "$TEST_DIRECTORY" -maxdepth 1 -type f -perm +111 \ ! -name '*~' \ ! -name 'lib/*' \ ! -name test-verbose \ ! -name assword-test \ | sed 's,.*/,,' | sort) test_expect_equal "$tests_in_suite" "$available" EXPECTED=$TEST_DIRECTORY/lib/test.expected-output suppress_diff_date() { sed -e 's/\(.*\-\-\- test-verbose\.4\.\expected\).*/\1/' \ -e 's/\(.*\+\+\+ test-verbose\.4\.\output\).*/\1/' } test_begin_subtest "Ensure that test output is suppressed unless the test fails" output=$(cd $TEST_DIRECTORY; ./test-verbose 2>&1 | suppress_diff_date) expected=$(cat $EXPECTED/test-verbose-no | suppress_diff_date) test_expect_equal "$output" "$expected" test_begin_subtest "Ensure that -v does not suppress test output" output=$(cd $TEST_DIRECTORY; ./test-verbose -v 2>&1 | suppress_diff_date) expected=$(cat $EXPECTED/test-verbose-yes | suppress_diff_date) # Do not include the results of test-verbose in totals rm $TEST_DIRECTORY/test-results/test-verbose-* rm -r $TEST_DIRECTORY/tmp.test-verbose test_expect_equal "$output" "$expected" ################################################################ test_done assword-0.8/test/cli000077500000000000000000000034221233306157700145270ustar00rootroot00000000000000#!/usr/bin/env bash test_description='cli' . lib/test-lib.sh ################################################################ test_expect_code 10 'dump non-existant db' \ 'assword dump' test_expect_success 'add first entry' \ 'assword add foo@bar' test_expect_success 'add second entry' \ "assword add 'baz asdf Dokw okb 32438uoijdf'" test_expect_code 1 'add existing context' \ 'assword add foo@bar' test_begin_subtest "dump all entries" assword dump | sed 's/"date": ".*"/FOO/g' >OUTPUT cat <EXPECTED { "baz asdf Dokw okb 32438uoijdf": { FOO }, "foo@bar": { FOO } } EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "dump search 0" assword dump foo | sed 's/"date": ".*"/FOO/g' >OUTPUT cat <EXPECTED { "foo@bar": { FOO } } EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "dump search 1" assword dump asdf | sed 's/"date": ".*"/FOO/g' >OUTPUT cat <EXPECTED { "baz asdf Dokw okb 32438uoijdf": { FOO } } EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "dump search 2" assword dump ba | sed 's/"date": ".*"/FOO/g' >OUTPUT cat <EXPECTED { "baz asdf Dokw okb 32438uoijdf": { FOO }, "foo@bar": { FOO } } EOF test_expect_equal_file OUTPUT EXPECTED test_expect_code 1 'add existing context' 'assword add foo@bar' test_expect_code 1 'replace non-existing context' \ 'assword replace aaaa' test_expect_code 1 'remove non-existant entry' 'assword remove aaaa' test_begin_subtest "remove entry" echo yes | assword remove foo@bar assword dump | sed 's/"date": ".*"/FOO/g' >OUTPUT cat <EXPECTED { "baz asdf Dokw okb 32438uoijdf": { FOO } } EOF test_expect_equal_file OUTPUT EXPECTED ################################################################ test_done assword-0.8/test/gnupg/000077500000000000000000000000001233306157700151515ustar00rootroot00000000000000assword-0.8/test/gnupg/pubring.gpg000066400000000000000000000012521233306157700173160ustar00rootroot00000000000000S_.ԙ.JMXN 0gJȭ?wXs*fKu$ PX5Jݣ+m@C{hoAnoz 6jlA{ C`u}/B4RP&~/4O'Assword Test User "S_.   /އ;bB^5n;!v.eOIp̚ndA"Az 2K#p5(C$ *cIJLjd(GuZ!.tphYnW3F :̄(ډgS_.l,0}(w9wB~l[Dw{ڡiM*Vga5wڣnC1#n`'x "=SHJƟW#Wh](M`-Mdž".k S_. /އ;`I[^/J" myJP2'Kpƃ,5$| ?HHi=#tmC,AD^ 5tUzb"F1C=VP]-Sa Hc#`U]Aassword-0.8/test/gnupg/secring.gpg000066400000000000000000000025021233306157700173010ustar00rootroot00000000000000S_.ԙ.JMXN 0gJȭ?wXs*fKu$ PX5Jݣ+m@C{hoAnoz 6jlA{ C`u}/B4RP&~/4OKl^PVpG |sFkeJ/vtE>,1XḧQQ9]8>]nݒS6\ubQjJaJSԮ)!懗ә,YN#f T1饡f%rĠM5tGj@ L|vkWpɵ׷iqƿ=i& 2Ҵd:a yT6 Ł93l>7<(fUЧmܶ7\K-QͶii L 1 κj +iw7'!ilxd5Qi<.x_QHss'Assword Test User "S_.   /އ;bB^5n;!v.eOIp̚ndA"Az 2K#p5(C$ *cIJLjd(GuZ!.tphYnW3F :̄(ډgS_.l,0}(w9wB~l[Dw{ڡiM*Vga5wڣnC1#n`'x "=SHJƟW#Wh](M`-Mdž".k/9BE2`-\Gvv `b#o =>S0єۗHЋ׏#1yQvw֒I//+0ZͿ\䷖?nm/:$hC|ߚ b@B e>k3{'*`j-t딵t0{vrE~sܺsB4pzS~cُ,Pd # for use by aLIGO Guardian if [ ${BASH_VERSINFO[0]} -lt 4 ]; then echo "Error: The notmuch test suite requires a bash version >= 4.0" echo "due to use of associative arrays within the test suite." echo "Please try again with a newer bash (or help us fix the" echo "test suite to be more portable). Thanks." exit 1 fi # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too. case "$GIT_TEST_TEE_STARTED, $* " in done,*) # do not redirect again ;; *' --tee '*|*' --va'*) mkdir -p test-results BASE=test-results/$(basename "$0" .sh) (GIT_TEST_TEE_STARTED=done ${SHELL-sh} "$0" "$@" 2>&1; echo $? > $BASE.exit) | tee $BASE.out test "$(cat $BASE.exit)" = 0 exit ;; esac # Keep the original TERM for say_color and test_emacs ORIGINAL_TERM=$TERM # For repeatability, reset the environment to known value. LANG=C LC_ALL=C PAGER=cat TZ=UTC TERM=dumb export LANG LC_ALL PAGER TERM TZ GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u} TEST_EMACS=${TEST_EMACS:-${EMACS:-emacs}} # Protect ourselves from common misconfiguration to export # CDPATH into the environment unset CDPATH unset GREP_OPTIONS # Convenience # # A regexp to match 5 and 40 hexdigits _x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' _x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05" _x04='[0-9a-f][0-9a-f][0-9a-f][0-9a-f]' _x32="$_x04$_x04$_x04$_x04$_x04$_x04$_x04$_x04" # Each test should start with something like this, after copyright notices: # # test_description='Description of this test... # This test checks if command xyzzy does the right thing... # ' # . ./test-lib.sh [ "x$ORIGINAL_TERM" != "xdumb" ] && ( TERM=$ORIGINAL_TERM && export TERM && [ -t 1 ] && tput bold >/dev/null 2>&1 && tput setaf 1 >/dev/null 2>&1 && tput sgr0 >/dev/null 2>&1 ) && color=t while test "$#" -ne 0 do case "$1" in -d|--d|--de|--deb|--debu|--debug) debug=t; shift ;; -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) immediate=t; shift ;; -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) GIT_TEST_LONG=t; export GIT_TEST_LONG; shift ;; -h|--h|--he|--hel|--help) help=t; shift ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t; shift ;; -q|--q|--qu|--qui|--quie|--quiet) quiet=t; shift ;; --with-dashes) with_dashes=t; shift ;; --no-color) color=; shift ;; --no-python) # noop now... shift ;; --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) valgrind=t; verbose=t; shift ;; --tee) shift ;; # was handled already --root=*) root=$(expr "z$1" : 'z[^=]*=\(.*\)') shift ;; *) echo "error: unknown test option '$1'" >&2; exit 1 ;; esac done if test -n "$debug"; then print_subtest () { printf " %-4s" "[$((test_count - 1))]" } else print_subtest () { true } fi if test -n "$color"; then say_color () { ( TERM=$ORIGINAL_TERM export TERM case "$1" in error) tput bold; tput setaf 1;; # bold red skip) tput bold; tput setaf 2;; # bold green pass) tput setaf 2;; # green info) tput setaf 3;; # brown *) test -n "$quiet" && return;; esac shift printf " " printf "$@" tput sgr0 print_subtest ) } else say_color() { test -z "$1" && test -n "$quiet" && return shift printf " " printf "$@" print_subtest } fi error () { say_color error "error: $*\n" GIT_EXIT_OK=t exit 1 } say () { say_color info "$*" } test "${test_description}" != "" || error "Test script did not set test_description." if test "$help" = "t" then echo "Tests ${test_description}" exit 0 fi echo $(basename "$0"): "Testing ${test_description}" exec 5>&1 test_failure=0 test_count=0 test_fixed=0 test_broken=0 test_success=0 die () { code=$? rm -rf "$TEST_TMPDIR" if test -n "$GIT_EXIT_OK" then exit $code else echo >&5 "FATAL: Unexpected exit with code $code" exit 1 fi } GIT_EXIT_OK= # Note: TEST_TMPDIR *NOT* exported! TEST_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/test-$$.XXXXXX") trap 'die' EXIT test_decode_color () { sed -e 's/.\[1m//g' \ -e 's/.\[31m//g' \ -e 's/.\[32m//g' \ -e 's/.\[33m//g' \ -e 's/.\[34m//g' \ -e 's/.\[35m//g' \ -e 's/.\[36m//g' \ -e 's/.\[m//g' } q_to_nul () { perl -pe 'y/Q/\000/' } q_to_cr () { tr Q '\015' } append_cr () { sed -e 's/$/Q/' | tr Q '\015' } remove_cr () { tr '\015' Q | sed -e 's/Q$//' } test_begin_subtest () { if [ -n "$inside_subtest" ]; then exec 1>&6 2>&7 # Restore stdout and stderr error "bug in test script: Missing test_expect_equal in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}" fi test_subtest_name="$1" test_reset_state_ # Remember stdout and stderr file descriptors and redirect test # output to the previously prepared file descriptors 3 and 4 (see # below) if test "$verbose" != "t"; then exec 4>test.output 3>&4; fi exec 6>&1 7>&2 >&3 2>&4 inside_subtest=t } # Pass test if two arguments match # # Note: Unlike all other test_expect_* functions, this function does # not accept a test name. Instead, the caller should call # test_begin_subtest before calling this function in order to set the # name. test_expect_equal () { exec 1>&6 2>&7 # Restore stdout and stderr inside_subtest= test "$#" = 3 && { prereq=$1; shift; } || prereq= test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_equal" output="$1" expected="$2" if ! test_skip "$test_subtest_name" then if [ "$output" = "$expected" ]; then test_ok_ "$test_subtest_name" else testname=$this_test.$test_count echo "$expected" > $testname.expected echo "$output" > $testname.output test_failure_ "$test_subtest_name" "$(diff -u $testname.expected $testname.output)" fi fi } # Like test_expect_equal, but takes two filenames. test_expect_equal_file () { exec 1>&6 2>&7 # Restore stdout and stderr inside_subtest= test "$#" = 3 && { prereq=$1; shift; } || prereq= test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_equal" output="$1" expected="$2" if ! test_skip "$test_subtest_name" then if diff -q "$expected" "$output" >/dev/null ; then test_ok_ "$test_subtest_name" else testname=$this_test.$test_count cp "$output" $testname.output cp "$expected" $testname.expected test_failure_ "$test_subtest_name" "$(diff -u $testname.expected $testname.output)" fi fi } # Like test_expect_equal, but arguments are JSON expressions to be # canonicalized before diff'ing. If an argument cannot be parsed, it # is used unchanged so that there's something to diff against. test_expect_equal_json () { output=$(echo "$1" | python -mjson.tool || echo "$1") expected=$(echo "$2" | python -mjson.tool || echo "$2") shift 2 test_expect_equal "$output" "$expected" "$@" } # Use test_set_prereq to tell that a particular prerequisite is available. # The prerequisite can later be checked for in two ways: # # - Explicitly using test_have_prereq. # # - Implicitly by specifying the prerequisite tag in the calls to # test_expect_{success,failure,code}. # # The single parameter is the prerequisite tag (a simple word, in all # capital letters by convention). test_set_prereq () { satisfied="$satisfied$1 " } satisfied=" " test_have_prereq () { case $satisfied in *" $1 "*) : yes, have it ;; *) ! : nope ;; esac } # declare prerequisite for the given external binary test_declare_external_prereq () { binary="$1" test "$#" = 2 && name=$2 || name="$binary(1)" hash $binary 2>/dev/null || eval " test_missing_external_prereq_${binary}_=t $binary () { echo -n \"\$test_subtest_missing_external_prereqs_ \" | grep -qe \" $name \" || test_subtest_missing_external_prereqs_=\"\$test_subtest_missing_external_prereqs_ $name\" false }" } # Explicitly require external prerequisite. Useful when binary is # called indirectly (e.g. from emacs). # Returns success if dependency is available, failure otherwise. test_require_external_prereq () { binary="$1" if [ "$(eval echo -n \$test_missing_external_prereq_${binary}_)" = t ]; then # dependency is missing, call the replacement function to note it eval "$binary" else true fi } # You are not expected to call test_ok_ and test_failure_ directly, use # the text_expect_* functions instead. test_ok_ () { if test "$test_subtest_known_broken_" = "t"; then test_known_broken_ok_ "$@" return fi test_success=$(($test_success + 1)) say_color pass "%-6s" "PASS" echo " $@" } test_failure_ () { if test "$test_subtest_known_broken_" = "t"; then test_known_broken_failure_ "$@" return fi test_failure=$(($test_failure + 1)) test_failure_message_ "FAIL" "$@" test "$immediate" = "" || { GIT_EXIT_OK=t; exit 1; } return 1 } test_failure_message_ () { say_color error "%-6s" "$1" echo " $2" shift 2 echo "$@" | sed -e 's/^/ /' if test "$verbose" != "t"; then cat test.output; fi } test_known_broken_ok_ () { test_reset_state_ test_fixed=$(($test_fixed+1)) say_color pass "%-6s" "FIXED" echo " $@" } test_known_broken_failure_ () { test_reset_state_ test_broken=$(($test_broken+1)) test_failure_message_ "BROKEN" "$@" return 1 } test_debug () { test "$debug" = "" || eval "$1" } test_run_ () { test_cleanup=: if test "$verbose" != "t"; then exec 4>test.output 3>&4; fi eval >&3 2>&4 "$1" eval_ret=$? eval >&3 2>&4 "$test_cleanup" return 0 } test_skip () { test_count=$(($test_count+1)) to_skip= for skp in $GUARD_SKIP_TESTS do case $this_test.$test_count in $skp) to_skip=t esac done if test -z "$to_skip" && test -n "$prereq" && ! test_have_prereq "$prereq" then to_skip=t fi case "$to_skip" in t) test_report_skip_ "$@" ;; *) test_check_missing_external_prereqs_ "$@" ;; esac } test_check_missing_external_prereqs_ () { if test -n "$test_subtest_missing_external_prereqs_"; then say_color skip >&1 "missing prerequisites:" echo "$test_subtest_missing_external_prereqs_" >&1 test_report_skip_ "$@" else false fi } test_report_skip_ () { test_reset_state_ say_color skip >&3 "skipping test:" echo " $@" >&3 say_color skip "%-6s" "SKIP" echo " $1" } test_subtest_known_broken () { test_subtest_known_broken_=t } test_expect_success () { test "$#" = 3 && { prereq=$1; shift; } || prereq= test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test-expect-success" test_reset_state_ if ! test_skip "$@" then test_run_ "$2" run_ret="$?" # test_run_ may update missing external prerequisites test_check_missing_external_prereqs_ "$@" || if [ "$run_ret" = 0 -a "$eval_ret" = 0 ] then test_ok_ "$1" else test_failure_ "$@" fi fi } test_expect_code () { test "$#" = 4 && { prereq=$1; shift; } || prereq= test "$#" = 3 || error "bug in the test script: not 3 or 4 parameters to test-expect-code" test_reset_state_ if ! test_skip "$@" then test_run_ "$3" run_ret="$?" # test_run_ may update missing external prerequisites, test_check_missing_external_prereqs_ "$@" || if [ "$run_ret" = 0 -a "$eval_ret" = "$1" ] then test_ok_ "$2" else test_failure_ "$@" fi fi } # test_external runs external test scripts that provide continuous # test output about their progress, and succeeds/fails on # zero/non-zero exit code. It outputs the test output on stdout even # in non-verbose mode, and announces the external script with "* run # : ..." before running it. When providing relative paths, keep in # mind that all scripts run in "trash directory". # Usage: test_external description command arguments... # Example: test_external 'Perl API' perl ../path/to/test.pl test_external () { test "$#" = 4 && { prereq=$1; shift; } || prereq= test "$#" = 3 || error >&5 "bug in the test script: not 3 or 4 parameters to test_external" descr="$1" shift test_reset_state_ if ! test_skip "$descr" "$@" then # Announce the script to reduce confusion about the # test output that follows. say_color "" " run $test_count: $descr ($*)" # Run command; redirect its stderr to &4 as in # test_run_, but keep its stdout on our stdout even in # non-verbose mode. "$@" 2>&4 if [ "$?" = 0 ] then test_ok_ "$descr" else test_failure_ "$descr" "$@" fi fi } # Like test_external, but in addition tests that the command generated # no output on stderr. test_external_without_stderr () { # The temporary file has no (and must have no) security # implications. tmp="$TMPDIR"; if [ -z "$tmp" ]; then tmp=/tmp; fi stderr="$tmp/git-external-stderr.$$.tmp" test_external "$@" 4> "$stderr" [ -f "$stderr" ] || error "Internal error: $stderr disappeared." descr="no stderr: $1" shift if [ ! -s "$stderr" ]; then rm "$stderr" test_ok_ "$descr" else if [ "$verbose" = t ]; then output=`echo; echo Stderr is:; cat "$stderr"` else output= fi # rm first in case test_failure exits. rm "$stderr" test_failure_ "$descr" "$@" "$output" fi } # This is not among top-level (test_expect_success) # but is a prefix that can be used in the test script, like: # # test_expect_success 'complain and die' ' # do something && # do something else && # test_must_fail git checkout ../outerspace # ' # # Writing this as "! git checkout ../outerspace" is wrong, because # the failure could be due to a segv. We want a controlled failure. test_must_fail () { "$@" test $? -gt 0 -a $? -le 129 -o $? -gt 192 } # test_cmp is a helper function to compare actual and expected output. # You can use it like: # # test_expect_success 'foo works' ' # echo expected >expected && # foo >actual && # test_cmp expected actual # ' # # This could be written as either "cmp" or "diff -u", but: # - cmp's output is not nearly as easy to read as diff -u # - not all diff versions understand "-u" test_cmp() { $GIT_TEST_CMP "$@" } # This function can be used to schedule some commands to be run # unconditionally at the end of the test to restore sanity: # # test_expect_success 'test core.capslock' ' # git config core.capslock true && # test_when_finished "git config --unset core.capslock" && # hello world # ' # # That would be roughly equivalent to # # test_expect_success 'test core.capslock' ' # git config core.capslock true && # hello world # git config --unset core.capslock # ' # # except that the greeting and config --unset must both succeed for # the test to pass. test_when_finished () { test_cleanup="{ $* } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" } test_done () { GIT_EXIT_OK=t test_results_dir="$TEST_DIRECTORY/test-results" mkdir -p "$test_results_dir" test_results_path="$test_results_dir/${0%.sh}-$$" echo "total $test_count" >> $test_results_path echo "success $test_success" >> $test_results_path echo "fixed $test_fixed" >> $test_results_path echo "broken $test_broken" >> $test_results_path echo "failed $test_failure" >> $test_results_path echo "" >> $test_results_path echo [ -n "$EMACS_SERVER" ] && test_emacs '(kill-emacs)' if [ "$test_failure" = "0" ]; then if [ "$test_broken" = "0" ]; then # This seems to be unfortunately necessary when # running tests on NFS rm -rf "$remove_tmp" 2>/dev/null || true rm -rf "$remove_tmp" fi exit 0 else exit 1 fi } test_python() { (echo "import sys; _orig_stdout=sys.stdout; sys.stdout=open('OUTPUT', 'w')"; cat) \ | python - } test_reset_state_ () { test -z "$test_init_done_" && test_init_ test_subtest_known_broken_= test_subtest_missing_external_prereqs_= } # called once before the first subtest test_init_ () { test_init_done_=t # skip all tests if there were external prerequisites missing during init test_check_missing_external_prereqs_ "all tests in $this_test" && test_done } # Test the binaries we have just built. The tests are kept in # test/ subdirectory and are run in 'trash directory' subdirectory. TEST_DIRECTORY=$(pwd) export PATH # Test repository test="tmp.$(basename "$0" .sh)" test -n "$root" && test="$root/$test" case "$test" in /*) TMP_DIRECTORY="$test" ;; *) TMP_DIRECTORY="$TEST_DIRECTORY/$test" ;; esac test ! -z "$debug" || remove_tmp=$TMP_DIRECTORY rm -fr "$test" || { GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area" exit 1 } mkdir -p "${test}" # load local test library . lib/test-local.sh # Use -P to resolve symlinks in our working directory so that the cwd # in subprocesses like git equals our $PWD (for pathname comparisons). cd -P "$test" || error "Cannot setup test environment" if test "$verbose" = "t" then exec 4>&2 3>&1 else exec 4>test.output 3>&4 fi this_test=${0##*/} for skp in $GUARD_SKIP_TESTS do to_skip= for skp in $GUARD_SKIP_TESTS do case "$this_test" in $skp) to_skip=t esac done case "$to_skip" in t) say_color skip >&3 "skipping test $this_test altogether" say_color skip "skip all tests in $this_test" test_done esac done # Provide an implementation of the 'yes' utility yes () { if test $# = 0 then y=y else y="$*" fi while echo "$y" do : done } # Fix some commands on Windows case $(uname -s) in *MINGW*) # Windows has its own (incompatible) sort and find sort () { /usr/bin/sort "$@" } find () { /usr/bin/find "$@" } sum () { md5sum "$@" } # git sees Windows-style pwd pwd () { builtin pwd -W } # no POSIX permissions # backslashes in pathspec are converted to '/' # exec does not inherit the PID ;; *) test_set_prereq POSIXPERM test_set_prereq BSLASHPSPEC test_set_prereq EXECKEEPSPID ;; esac test -z "$NO_PERL" && test_set_prereq PERL test -z "$NO_PYTHON" && test_set_prereq PYTHON # test whether the filesystem supports symbolic links ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS rm -f y assword-0.8/test/lib/test-local.sh000066400000000000000000000006401233306157700172020ustar00rootroot00000000000000# declare prerequisites for external binaries used in tests # test_declare_external_prereq python # test_declare_external_prereq python2 export SRC_DIRECTORY=$(cd "$TEST_DIRECTORY"/.. && pwd) export PATH="$SRC_DIRECTORY":$PATH export PYTHONPATH="$SRC_DIRECTORY":$PYTHONPATH export ASSWORD_DB=${TMP_DIRECTORY}/db export GNUPGHOME="$TEST_DIRECTORY"/gnupg export ASSWORD_KEYID=6D3C87EB41EDE1EC8C7CFAFB032FDE87A6EBD73B assword-0.8/test/lib/test.expected-output/000077500000000000000000000000001233306157700207145ustar00rootroot00000000000000assword-0.8/test/lib/test.expected-output/test-verbose-no000066400000000000000000000011451233306157700236740ustar00rootroot00000000000000test-verbose: Testing the verbosity options of the test framework itself. PASS print something in test_expect_success and pass FAIL print something in test_expect_success and fail echo "hello stdout" && echo "hello stderr" >&2 && false hello stdout hello stderr PASS print something between test_begin_subtest and test_expect_equal and pass FAIL print something test_begin_subtest and test_expect_equal and fail --- test-verbose.4.expected 2010-11-14 21:41:12.738189710 +0000 +++ test-verbose.4.output 2010-11-14 21:41:12.738189710 +0000 @@ -1 +1 @@ -b +a hello stdout hello stderr assword-0.8/test/lib/test.expected-output/test-verbose-yes000066400000000000000000000012311233306157700240540ustar00rootroot00000000000000test-verbose: Testing the verbosity options of the test framework itself. hello stdout hello stderr PASS print something in test_expect_success and pass hello stdout hello stderr FAIL print something in test_expect_success and fail echo "hello stdout" && echo "hello stderr" >&2 && false hello stdout hello stderr PASS print something between test_begin_subtest and test_expect_equal and pass hello stdout hello stderr FAIL print something test_begin_subtest and test_expect_equal and fail --- test-verbose.4.expected 2010-11-14 21:41:06.650023289 +0000 +++ test-verbose.4.output 2010-11-14 21:41:06.650023289 +0000 @@ -1 +1 @@ -b +a assword-0.8/test/library000077500000000000000000000037401233306157700154270ustar00rootroot00000000000000#!/usr/bin/env bash test_description='library interface' . lib/test-lib.sh ################################################################ test_begin_subtest "create db" python - <OUTPUT import assword db = assword.Database("$ASSWORD_DB") print db print db.version db.add("foo@bar") print 'foo@bar' in db db.save('$ASSWORD_KEYID') EOF cat <EXPECTED 1 True EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "decrypt db" python - <OUTPUT import assword db = assword.Database("$ASSWORD_DB") print db print db.version print 'foo@bar' in db EOF cat <EXPECTED 1 True EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "add second entry" python - <OUTPUT import assword db = assword.Database("$ASSWORD_DB", '$ASSWORD_KEYID') db.add("aaaa") print 'aaaa' in db for e in sorted(db): print e db.save() EOF cat <EXPECTED True aaaa foo@bar EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "replace entry" python - <OUTPUT import assword db = assword.Database("$ASSWORD_DB", '$ASSWORD_KEYID') p1 = db['aaaa']['password'] db.replace("aaaa") print 'aaaa' in db p2 = db['aaaa']['password'] print p1 == p2 db.save() EOF cat <EXPECTED True False EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "remove entry" python - <OUTPUT import assword db = assword.Database("$ASSWORD_DB", '$ASSWORD_KEYID') print 'foo@bar' in db db.remove("foo@bar") print 'foo@bar' in db db.save() EOF python - <>OUTPUT import assword db = assword.Database("$ASSWORD_DB", '$ASSWORD_KEYID') for e in db: print e EOF cat <EXPECTED True False aaaa EOF test_expect_equal_file OUTPUT EXPECTED ################################################################ test_done assword-0.8/test/test-verbose000077500000000000000000000013151233306157700164010ustar00rootroot00000000000000#!/usr/bin/env bash test_description='the verbosity options of the test framework itself.' . lib/test-lib.sh test_expect_success 'print something in test_expect_success and pass' ' echo "hello stdout" && echo "hello stderr" >&2 && true ' test_expect_success 'print something in test_expect_success and fail' ' echo "hello stdout" && echo "hello stderr" >&2 && false ' test_begin_subtest 'print something between test_begin_subtest and test_expect_equal and pass' echo "hello stdout" echo "hello stderr" >&2 test_expect_equal "a" "a" test_begin_subtest 'print something test_begin_subtest and test_expect_equal and fail' echo "hello stdout" echo "hello stderr" >&2 test_expect_equal "a" "b" test_done