diff options
Diffstat (limited to 'source/stf')
-rw-r--r-- | source/stf/.cvsignore | 2 | ||||
-rw-r--r-- | source/stf/README.stf | 3 | ||||
-rw-r--r-- | source/stf/comfychair.py | 445 | ||||
-rwxr-xr-x | source/stf/example.py | 38 | ||||
-rwxr-xr-x | source/stf/info3cache.py | 54 | ||||
-rw-r--r-- | source/stf/notes.txt | 175 | ||||
-rwxr-xr-x | source/stf/osver.py | 55 | ||||
-rwxr-xr-x | source/stf/pythoncheck.py | 48 | ||||
-rw-r--r-- | source/stf/sambalib.py | 41 | ||||
-rwxr-xr-x | source/stf/smbcontrol.py | 238 | ||||
-rwxr-xr-x | source/stf/spoolss.py | 288 | ||||
-rw-r--r-- | source/stf/standardcheck.py | 34 | ||||
-rwxr-xr-x | source/stf/stf.py | 101 | ||||
-rwxr-xr-x | source/stf/strings.py | 151 | ||||
-rwxr-xr-x | source/stf/test.py | 33 | ||||
-rw-r--r-- | source/stf/unicodenames.py | 33 |
16 files changed, 1739 insertions, 0 deletions
diff --git a/source/stf/.cvsignore b/source/stf/.cvsignore new file mode 100644 index 00000000000..bcf41506063 --- /dev/null +++ b/source/stf/.cvsignore @@ -0,0 +1,2 @@ +*.pyc +testtmp diff --git a/source/stf/README.stf b/source/stf/README.stf new file mode 100644 index 00000000000..3fbd33cb6cc --- /dev/null +++ b/source/stf/README.stf @@ -0,0 +1,3 @@ +This directory contains the Samba Testing Framework, a Python-based +system for exercising Samba in various ways. It is quite small at the +moment. diff --git a/source/stf/comfychair.py b/source/stf/comfychair.py new file mode 100644 index 00000000000..522f9bedeba --- /dev/null +++ b/source/stf/comfychair.py @@ -0,0 +1,445 @@ +#! /usr/bin/env python + +# Copyright (C) 2002, 2003 by Martin Pool <mbp@samba.org> +# Copyright (C) 2003 by Tim Potter <tpot@samba.org> +# +# This program 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 2 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; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + +"""comfychair: a Python-based instrument of software torture. + +Copyright (C) 2002, 2003 by Martin Pool <mbp@samba.org> +Copyright (C) 2003 by Tim Potter <tpot@samba.org> + +This is a test framework designed for testing programs written in +Python, or (through a fork/exec interface) any other language. + +For more information, see the file README.comfychair. + +To run a test suite based on ComfyChair, just run it as a program. +""" + +import sys, re + + +class TestCase: + """A base class for tests. This class defines required functions which + can optionally be overridden by subclasses. It also provides some + utility functions for""" + + def __init__(self): + self.test_log = "" + self.background_pids = [] + self._cleanups = [] + self._enter_rundir() + self._save_environment() + self.add_cleanup(self.teardown) + + + # -------------------------------------------------- + # Save and restore directory + def _enter_rundir(self): + import os + self.basedir = os.getcwd() + self.add_cleanup(self._restore_directory) + self.rundir = os.path.join(self.basedir, + 'testtmp', + self.__class__.__name__) + self.tmpdir = os.path.join(self.rundir, 'tmp') + os.system("rm -fr %s" % self.rundir) + os.makedirs(self.tmpdir) + os.system("mkdir -p %s" % self.rundir) + os.chdir(self.rundir) + + def _restore_directory(self): + import os + os.chdir(self.basedir) + + # -------------------------------------------------- + # Save and restore environment + def _save_environment(self): + import os + self._saved_environ = os.environ.copy() + self.add_cleanup(self._restore_environment) + + def _restore_environment(self): + import os + os.environ.clear() + os.environ.update(self._saved_environ) + + + def setup(self): + """Set up test fixture.""" + pass + + def teardown(self): + """Tear down test fixture.""" + pass + + def runtest(self): + """Run the test.""" + pass + + + def add_cleanup(self, c): + """Queue a cleanup to be run when the test is complete.""" + self._cleanups.append(c) + + + def fail(self, reason = ""): + """Say the test failed.""" + raise AssertionError(reason) + + + ############################################################# + # Requisition methods + + def require(self, predicate, message): + """Check a predicate for running this test. + +If the predicate value is not true, the test is skipped with a message explaining +why.""" + if not predicate: + raise NotRunError, message + + def require_root(self): + """Skip this test unless run by root.""" + import os + self.require(os.getuid() == 0, + "must be root to run this test") + + ############################################################# + # Assertion methods + + def assert_(self, expr, reason = ""): + if not expr: + raise AssertionError(reason) + + def assert_equal(self, a, b): + if not a == b: + raise AssertionError("assertEquals failed: %s" % `(a, b)`) + + def assert_notequal(self, a, b): + if a == b: + raise AssertionError("assertNotEqual failed: %s" % `(a, b)`) + + def assert_re_match(self, pattern, s): + """Assert that a string matches a particular pattern + + Inputs: + pattern string: regular expression + s string: to be matched + + Raises: + AssertionError if not matched + """ + if not re.match(pattern, s): + raise AssertionError("string does not match regexp\n" + " string: %s\n" + " re: %s" % (`s`, `pattern`)) + + def assert_re_search(self, pattern, s): + """Assert that a string *contains* a particular pattern + + Inputs: + pattern string: regular expression + s string: to be searched + + Raises: + AssertionError if not matched + """ + if not re.search(pattern, s): + raise AssertionError("string does not contain regexp\n" + " string: %s\n" + " re: %s" % (`s`, `pattern`)) + + + def assert_no_file(self, filename): + import os.path + assert not os.path.exists(filename), ("file exists but should not: %s" % filename) + + + ############################################################# + # Methods for running programs + + def runcmd_background(self, cmd): + import os + self.test_log = self.test_log + "Run in background:\n" + `cmd` + "\n" + pid = os.fork() + if pid == 0: + # child + try: + os.execvp("/bin/sh", ["/bin/sh", "-c", cmd]) + finally: + os._exit(127) + self.test_log = self.test_log + "pid: %d\n" % pid + return pid + + + def runcmd(self, cmd, expectedResult = 0): + """Run a command, fail if the command returns an unexpected exit + code. Return the output produced.""" + rc, output, stderr = self.runcmd_unchecked(cmd) + if rc != expectedResult: + raise AssertionError("""command returned %d; expected %s: \"%s\" +stdout: +%s +stderr: +%s""" % (rc, expectedResult, cmd, output, stderr)) + + return output, stderr + + + def run_captured(self, cmd): + """Run a command, capturing stdout and stderr. + + Based in part on popen2.py + + Returns (waitstatus, stdout, stderr).""" + import os, types + pid = os.fork() + if pid == 0: + # child + try: + pid = os.getpid() + openmode = os.O_WRONLY|os.O_CREAT|os.O_TRUNC + + outfd = os.open('%d.out' % pid, openmode, 0666) + os.dup2(outfd, 1) + os.close(outfd) + + errfd = os.open('%d.err' % pid, openmode, 0666) + os.dup2(errfd, 2) + os.close(errfd) + + if isinstance(cmd, types.StringType): + cmd = ['/bin/sh', '-c', cmd] + + os.execvp(cmd[0], cmd) + finally: + os._exit(127) + else: + # parent + exited_pid, waitstatus = os.waitpid(pid, 0) + stdout = open('%d.out' % pid).read() + stderr = open('%d.err' % pid).read() + return waitstatus, stdout, stderr + + + def runcmd_unchecked(self, cmd, skip_on_noexec = 0): + """Invoke a command; return (exitcode, stdout, stderr)""" + import os + waitstatus, stdout, stderr = self.run_captured(cmd) + assert not os.WIFSIGNALED(waitstatus), \ + ("%s terminated with signal %d" % (`cmd`, os.WTERMSIG(waitstatus))) + rc = os.WEXITSTATUS(waitstatus) + self.test_log = self.test_log + ("""Run command: %s +Wait status: %#x (exit code %d, signal %d) +stdout: +%s +stderr: +%s""" % (cmd, waitstatus, os.WEXITSTATUS(waitstatus), os.WTERMSIG(waitstatus), + stdout, stderr)) + if skip_on_noexec and rc == 127: + # Either we could not execute the command or the command + # returned exit code 127. According to system(3) we can't + # tell the difference. + raise NotRunError, "could not execute %s" % `cmd` + return rc, stdout, stderr + + + def explain_failure(self, exc_info = None): + print "test_log:" + print self.test_log + + + def log(self, msg): + """Log a message to the test log. This message is displayed if + the test fails, or when the runtests function is invoked with + the verbose option.""" + self.test_log = self.test_log + msg + "\n" + + +class NotRunError(Exception): + """Raised if a test must be skipped because of missing resources""" + def __init__(self, value = None): + self.value = value + + +def _report_error(case, debugger): + """Ask the test case to explain failure, and optionally run a debugger + + Input: + case TestCase instance + debugger if true, a debugger function to be applied to the traceback +""" + import sys + ex = sys.exc_info() + print "-----------------------------------------------------------------" + if ex: + import traceback + traceback.print_exc(file=sys.stdout) + case.explain_failure() + print "-----------------------------------------------------------------" + + if debugger: + tb = ex[2] + debugger(tb) + + +def runtests(test_list, verbose = 0, debugger = None): + """Run a series of tests. + + Inputs: + test_list sequence of TestCase classes + verbose print more information as testing proceeds + debugger debugger object to be applied to errors + + Returns: + unix return code: 0 for success, 1 for failures, 2 for test failure + """ + import traceback + ret = 0 + for test_class in test_list: + print "%-30s" % _test_name(test_class), + # flush now so that long running tests are easier to follow + sys.stdout.flush() + + obj = None + try: + try: # run test and show result + obj = test_class() + obj.setup() + obj.runtest() + print "OK" + except KeyboardInterrupt: + print "INTERRUPT" + _report_error(obj, debugger) + ret = 2 + break + except NotRunError, msg: + print "NOTRUN, %s" % msg.value + except: + print "FAIL" + _report_error(obj, debugger) + ret = 1 + finally: + while obj and obj._cleanups: + try: + apply(obj._cleanups.pop()) + except KeyboardInterrupt: + print "interrupted during teardown" + _report_error(obj, debugger) + ret = 2 + break + except: + print "error during teardown" + _report_error(obj, debugger) + ret = 1 + # Display log file if we're verbose + if ret == 0 and verbose: + obj.explain_failure() + + return ret + + +def _test_name(test_class): + """Return a human-readable name for a test class. + """ + try: + return test_class.__name__ + except: + return `test_class` + + +def print_help(): + """Help for people running tests""" + import sys + print """%s: software test suite based on ComfyChair + +usage: + To run all tests, just run this program. To run particular tests, + list them on the command line. + +options: + --help show usage message + --list list available tests + --verbose, -v show more information while running tests + --post-mortem, -p enter Python debugger on error +""" % sys.argv[0] + + +def print_list(test_list): + """Show list of available tests""" + for test_class in test_list: + print " %s" % _test_name(test_class) + + +def main(tests, extra_tests=[]): + """Main entry point for test suites based on ComfyChair. + + inputs: + tests Sequence of TestCase subclasses to be run by default. + extra_tests Sequence of TestCase subclasses that are available but + not run by default. + +Test suites should contain this boilerplate: + + if __name__ == '__main__': + comfychair.main(tests) + +This function handles standard options such as --help and --list, and +by default runs all tests in the suggested order. + +Calls sys.exit() on completion. +""" + from sys import argv + import getopt, sys + + opt_verbose = 0 + debugger = None + + opts, args = getopt.getopt(argv[1:], 'pv', + ['help', 'list', 'verbose', 'post-mortem']) + for opt, opt_arg in opts: + if opt == '--help': + print_help() + return + elif opt == '--list': + print_list(tests + extra_tests) + return + elif opt == '--verbose' or opt == '-v': + opt_verbose = 1 + elif opt == '--post-mortem' or opt == '-p': + import pdb + debugger = pdb.post_mortem + + if args: + all_tests = tests + extra_tests + by_name = {} + for t in all_tests: + by_name[_test_name(t)] = t + which_tests = [] + for name in args: + which_tests.append(by_name[name]) + else: + which_tests = tests + + sys.exit(runtests(which_tests, verbose=opt_verbose, + debugger=debugger)) + + +if __name__ == '__main__': + print __doc__ diff --git a/source/stf/example.py b/source/stf/example.py new file mode 100755 index 00000000000..96e34bf3d3b --- /dev/null +++ b/source/stf/example.py @@ -0,0 +1,38 @@ +#! /usr/bin/env python + +# Copyright (C) 2003 by Martin Pool <mbp@samba.org> +# +# This program 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 2 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; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + + +"""example of using ComfyChair""" + +import comfychair + +class OnePlusOne(comfychair.TestCase): + def runtest(self): + self.assert_(1 + 1 == 2) + +class FailTest(comfychair.TestCase): + def runtest(self): + self.assert_(1 + 1 == 3) + +tests = [OnePlusOne] +extra_tests = [FailTest] + +if __name__ == '__main__': + comfychair.main(tests, extra_tests=extra_tests) + diff --git a/source/stf/info3cache.py b/source/stf/info3cache.py new file mode 100755 index 00000000000..96d5a1d4596 --- /dev/null +++ b/source/stf/info3cache.py @@ -0,0 +1,54 @@ +#!/usr/bin/python +# +# Upon a winbindd authentication, test that an info3 record is cached in +# netsamlogon_cache.tdb and cache records are removed from winbindd_cache.tdb +# + +import comfychair, stf +from samba import tdb, winbind + +# +# We want to implement the following test on a win2k native mode domain. +# +# 1. trash netsamlogon_cache.tdb +# 2. wbinfo -r DOMAIN\Administrator [FAIL] +# 3. wbinfo --auth-crap DOMAIN\Administrator%password [PASS] +# 4. wbinfo -r DOMAIN\Administrator [PASS] +# +# Also for step 3 we want to try 'wbinfo --auth-smbd' and +# 'wbinfo --auth-plaintext' +# + +# +# TODO: To implement this test we need to be able to +# +# - pass username%password combination for an invidivual winbindd request +# (so we can get the administrator SID so we can clear the info3 cache) +# +# - start/restart winbindd (to trash the winbind cache) +# +# - from samba import dynconfig (to find location of info3 cache) +# +# - be able to modify the winbindd cache (to set/reset individual winbind +# cache entries) +# +# - have --auth-crap present in HEAD +# + +class WinbindAuthCrap(comfychair.TestCase): + def runtest(self): + raise comfychair.NotRunError, "not implemented" + +class WinbindAuthSmbd(comfychair.TestCase): + def runtest(self): + # Grr - winbindd in HEAD doesn't contain the auth_smbd function + raise comfychair.NotRunError, "no auth_smbd in HEAD" + +class WinbindAuthPlaintext(comfychair.TestCase): + def runtest(self): + raise comfychair.NotRunError, "not implemented" + +tests = [WinbindAuthCrap, WinbindAuthSmbd, WinbindAuthPlaintext] + +if __name__ == "__main__": + comfychair.main(tests) diff --git a/source/stf/notes.txt b/source/stf/notes.txt new file mode 100644 index 00000000000..68aca63c237 --- /dev/null +++ b/source/stf/notes.txt @@ -0,0 +1,175 @@ + -*- indented-text -*- + +(set lotus no) + + + +Notes on using comfychair with Samba (samba testing framework units): + +The tests need to rely on some external resources, such as + +If suitable resources are not available, need to skip particular +tests. Must include a message indicating what resources would be +needed to run that test. (e.g. must be root.) + +We want to be able to select and run particular subsets of tests, such +as "all winbind tests". + +We want to keep the number of configurable parameters down as much as +possible, to make it easy on people running the tests. + +Wherever possible, the tests should set up their preconditions, but a +few basic resources need to be provided by the people running the +tests. So for example, rather than asking the user for the name of a +non-root user, we should give the tests the administrator name and +password, and it can create a new user to use. + +This makes it simpler to get the tests running, and possible also +makes them more reproducible. + +In the future, rather than using NT machines provided by the test +person, we might have a way to drive VMWare non-persistent sessions, +to make tests even more tightly controlled. + + +Another design question is how to communicate this information to the +tests. If there's a lot of settings, then it might need to be stored +in a configuration file. + +However, if we succeed in cutting down the number of parameters, then +it might be straightforward to pass the information on the command +line or in an environment variable. + +Environment variables are probably better because they can't be seen +by other users, and they are more easily passed down through an +invocation of "make check". + + + +Notes on Samba Testing Framework for Unittests +---------------------------------------------- + +This is to be read after reading the notes.txt from comfychair. I'm +proposing a slightly more concrete description of what's described +there. + +The model of having tests require named resources looks useful for +incorporation into a framework that can be run by many people in +widely different environments. + +Some possible environments for running the test framework in are: + + - Casual downloader of Samba compiling from source and just wants + to run 'make check'. May only have one Unix machine and a + handful of clients. + + - Samba team member with access to a small number of other + machines or VMware sessions. + + - PSA developer who may not have intimate knowledge of Samba + internals and is only interested in testing against the PSA. + + - Non-team hacker wanting to run test suite after making small + hacks. + + - Build farm environment (loaner machine with no physical access + or root privilege). + + - HP BAT. + +Developers in most of these environments are also potential test case +authors. It should be easy for people unfamiliar with the framework +to write new tests and have them work. We should provide examples and +the existing tests should well written and understandable. + +Different types of tests: + + - Tests that check Samba internals and link against + libbigballofmud.so. For example: + + - Upper/lowercase string functions + - user_in_list() for large lists + + - Tests that use the Samba Python extensions. + + - Tests that execute Samba command line programs, for example + smbpasswd. + + - Tests that require other resources on the network such as domain + controllers or PSAs. + + - Tests that are performed on the documentation or the source code + such as: + + - grep for common spelling mistakes made by abartlet (-: + - grep for company copyright (IBM, HP) + + - Link to other existing testing frameworks (smbtorture, + abartlet's bash based build farm tests) + +I propose a TestResourceManager which would be instantiated by a test +case. The test case would require("resourcename") as part of its +constructor and raise a comfychair.NotRun exception if the resource +was not present. A TestResource class could be defined which could +read a configuration file or examine a environment variable and +register a resource only if some condition was satisfied. + +It would be nice to be able to completely separate the PSA testing +from the test framework. This would entail being able to define test +resources dynamically, possibly with a plugin type system. + +class TestResourceManager: + def __init__(self, name): + self.resources = {} + + def register(self, resource): + name = resource.name() + if self.resources.has_key(name): + raise "Test manager already has resource %s" % name + self.resources[name] = resource + + def require(self, resource_name): + if not self.resources.has_key(resource_name): + raise "Test manager does not have resources %s" % resource_name + +class TestResource: + def __init__(self, name): + self.name = name + + def name(self): + return self.name + +import os + +trm = TestResourceManager() + +if os.getuid() == 0: + trm.register(TestResource("root")) + +A config-o-matic Python module can take a list of machines and +administrator%password entries and classify them by operating system +version and service pack. These resources would be registered with +the TestResourceManager. + +Some random thoughts about named resources for network servers: + +require("nt4.sp3") +require("nt4.domaincontroller") +require("psa") + +Some kind of format for location of passwords, libraries: + +require("exec(smbpasswd)") +require("lib(bigballofmud)") + +maybe require("exec.smbpasswd") looks nicer... + +The require() function could return a dictionary of configuration +information or some handle to fetch dynamic information on. We may +need to create and destroy extra users or print queues. How to manage +cleanup of dynamic resources? + +Requirements for running stf: + + - Python, obviously + - Samba python extensions diff --git a/source/stf/osver.py b/source/stf/osver.py new file mode 100755 index 00000000000..68601fa7bb4 --- /dev/null +++ b/source/stf/osver.py @@ -0,0 +1,55 @@ +#!/usr/bin/python +# +# Utilities for determining the Windows operating system version remotely. +# + +from samba import srvsvc + +# Constants + +PLATFORM_UNKNOWN = 0 +PLATFORM_WIN9X = 1 +PLATFORM_NT4 = 2 +PLATFORM_NT5 = 3 # Windows 2000 + +def platform_name(platform_type): + + platform_names = { PLATFORM_UNKNOWN: "Unknown", + PLATFORM_WIN9X: "Windows 9x", + PLATFORM_NT4: "Windows NT", + PLATFORM_NT5: "Windows 2000" } + + if platform_names.has_key(platform_type): + return platform_names[platform_type] + + return "Unknown" + +def platform_type(info101): + """Determine the operating system type from a SRV_INFO_101.""" + + if info101['major_version'] == 4 and info101['minor_version'] == 0: + return PLATFORM_NT4 + + if info101['major_version'] == 5 and info101['minor_version'] == 0: + return PLATFORM_NT5 + + return PLATFORM_UNKNOWN + +def is_domain_controller(info101): + """Return true if the server_type field from a SRV_INFO_101 + indicates a domain controller.""" + return info101['server_type'] & srvsvc.SV_TYPE_DOMAIN_CTRL + +def os_version(name): + info = srvsvc.netservergetinfo("\\\\%s" % name, 101) + return platform_type(info) + +if __name__ == "__main__": + import sys + if len(sys.argv) != 2: + print "Usage: osver.py server" + sys.exit(0) + info = srvsvc.netservergetinfo("\\\\%s" % sys.argv[1], 101) + print "platform type = %d" % platform_type(info) + if is_domain_controller(info): + print "%s is a domain controller" % sys.argv[1] diff --git a/source/stf/pythoncheck.py b/source/stf/pythoncheck.py new file mode 100755 index 00000000000..398bb2c3d69 --- /dev/null +++ b/source/stf/pythoncheck.py @@ -0,0 +1,48 @@ +#! /usr/bin/python + +# Comfychair test cases for Samba python extensions + +# Copyright (C) 2003 by Tim Potter <tpot@samba.org> +# +# This program 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 2 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; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + +"""These tests are run by Samba's "make check".""" + +import sys, comfychair + +class ImportTest(comfychair.TestCase): + """Check that all modules can be imported without error.""" + def runtest(self): + python_modules = ['spoolss', 'lsa', 'samr', 'winbind', 'winreg', + 'srvsvc', 'tdb', 'smb', 'tdbpack'] + for m in python_modules: + try: + __import__('samba.%s' % m) + except ImportError, msg: + self.log(str(msg)) + self.fail('error importing %s module' % m) + +tests = [ImportTest] + +if __name__ == '__main__': + # Some magic to repend build directory to python path so we see the + # objects we have built and not previously installed stuff. + from distutils.util import get_platform + from os import getcwd + sys.path.insert(0, '%s/build/lib.%s-%s' % + (getcwd(), get_platform(), sys.version[0:3])) + + comfychair.main(tests) diff --git a/source/stf/sambalib.py b/source/stf/sambalib.py new file mode 100644 index 00000000000..13d38e2a281 --- /dev/null +++ b/source/stf/sambalib.py @@ -0,0 +1,41 @@ +#! /usr/bin/python + +# Comfychair test cases for Samba string functions. + +# Copyright (C) 2003 by Martin Pool <mbp@samba.org> +# +# This program 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 2 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; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + +"""Tests for Samba library functions.""" + +import sys, re, comfychair +from unicodenames import * + +class snprintf_Test(comfychair.TestCase): + def runtest(self): + # Everything is built in to the test + out, err = self.runcmd('t_snprintf') + +# Define the tests exported by this module +tests = [snprintf_Test] + +# Handle execution of this file as a main program +if __name__ == '__main__': + comfychair.main(tests) + +# Local variables: +# coding: utf-8 +# End: diff --git a/source/stf/smbcontrol.py b/source/stf/smbcontrol.py new file mode 100755 index 00000000000..30c331819c7 --- /dev/null +++ b/source/stf/smbcontrol.py @@ -0,0 +1,238 @@ +#!/usr/bin/python +# +# Test for smbcontrol command line argument handling. +# + +import comfychair + +class NoArgs(comfychair.TestCase): + """Test no arguments produces usage message.""" + def runtest(self): + out = self.runcmd("smbcontrol", expectedResult = 1) + self.assert_re_match("Usage: smbcontrol", out[1]) + +class OneArg(comfychair.TestCase): + """Test single argument produces usage message.""" + def runtest(self): + out = self.runcmd("smbcontrol foo", expectedResult = 1) + self.assert_re_match("Usage: smbcontrol", out[1]) + +class SmbdDest(comfychair.TestCase): + """Test the broadcast destination 'smbd'.""" + def runtest(self): + out = self.runcmd("smbcontrol smbd noop") + +class NmbdDest(comfychair.TestCase): + """Test the destination 'nmbd'.""" + def runtest(self): + # We need a way to start/stop/whatever nmbd + raise comfychair.NotRunError, "not implemented" + +class PidDest(comfychair.TestCase): + """Test a pid number destination'.""" + def runtest(self): + out = self.runcmd("smbcontrol 1234 noop") + +class SelfDest(comfychair.TestCase): + """Test the destination 'self'.""" + def runtest(self): + out = self.runcmd("smbcontrol self noop") + +class WinbinddDest(comfychair.TestCase): + """Test the destination 'winbindd'.""" + def runtest(self): + # We need a way to start/stop/whatever winbindd + raise comfychair.NotRunError, "not implemented" + +class BadDest(comfychair.TestCase): + """Test a bad destination.""" + def runtest(self): + out = self.runcmd("smbcontrol foo noop", expectedResult = 1) + +class BadCmd(comfychair.TestCase): + """Test a bad command.""" + def runtest(self): + out = self.runcmd("smbcontrol self spottyfoot", expectedResult = 1) + self.assert_re_match("smbcontrol: unknown command", out[1]); + +class NoArgCmdTest(comfychair.TestCase): + """A test class that tests a command with no argument.""" + def runtest(self): + self.require_root() + out = self.runcmd("smbcontrol self %s" % self.cmd) + out = self.runcmd("smbcontrol self %s spottyfoot" % self.cmd, + expectedResult = 1) + +class ForceElection(NoArgCmdTest): + """Test a force-election message.""" + def setup(self): + self.cmd = "force-election" + +class SamSync(NoArgCmdTest): + """Test a samsync message.""" + def setup(self): + self.cmd = "samsync" + +class SamRepl(NoArgCmdTest): + """Test a samrepl message.""" + def setup(self): + self.cmd = "samrepl" + +class DmallocChanged(NoArgCmdTest): + """Test a dmalloc-changed message.""" + def setup(self): + self.cmd = "dmalloc-log-changed" + +class DmallocMark(NoArgCmdTest): + """Test a dmalloc-mark message.""" + def setup(self): + self.cmd = "dmalloc-mark" + +class Shutdown(NoArgCmdTest): + """Test a shutdown message.""" + def setup(self): + self.cmd = "shutdown" + +class Ping(NoArgCmdTest): + """Test a ping message.""" + def setup(self): + self.cmd = "ping" + +class Debuglevel(NoArgCmdTest): + """Test a debuglevel message.""" + def setup(self): + self.cmd = "debuglevel" + +class OneArgCmdTest(comfychair.TestCase): + """A test class that tests a command with one argument.""" + def runtest(self): + self.require_root() + out = self.runcmd("smbcontrol self %s spottyfoot" % self.cmd) + out = self.runcmd("smbcontrol self %s" % self.cmd, expectedResult = 1) + +class DrvUpgrade(OneArgCmdTest): + """Test driver upgrade message.""" + def setup(self): + self.cmd = "drvupgrade" + +class CloseShare(OneArgCmdTest): + """Test close share message.""" + def setup(self): + self.cmd = "close-share" + +class Debug(OneArgCmdTest): + """Test a debug message.""" + def setup(self): + self.cmd = "debug" + +class PrintNotify(comfychair.TestCase): + """Test print notification commands.""" + def runtest(self): + + # No subcommand + + out = self.runcmd("smbcontrol self printnotify", expectedResult = 1) + self.assert_re_match("Must specify subcommand", out[1]); + + # Invalid subcommand name + + out = self.runcmd("smbcontrol self printnotify spottyfoot", + expectedResult = 1) + self.assert_re_match("Invalid subcommand", out[1]); + + # Queue commands + + for cmd in ["queuepause", "queueresume"]: + + out = self.runcmd("smbcontrol self printnotify %s" % cmd, + expectedResult = 1) + self.assert_re_match("Usage:", out[1]) + + out = self.runcmd("smbcontrol self printnotify %s spottyfoot" + % cmd) + + # Job commands + + for cmd in ["jobpause", "jobresume", "jobdelete"]: + + out = self.runcmd("smbcontrol self printnotify %s" % cmd, + expectedResult = 1) + self.assert_re_match("Usage:", out[1]) + + out = self.runcmd("smbcontrol self printnotify %s spottyfoot" + % cmd, expectedResult = 1) + self.assert_re_match("Usage:", out[1]) + + out = self.runcmd("smbcontrol self printnotify %s spottyfoot 123" + % cmd) + + # Printer properties + + out = self.runcmd("smbcontrol self printnotify printer", + expectedResult = 1) + self.assert_re_match("Usage", out[1]) + + out = self.runcmd("smbcontrol self printnotify printer spottyfoot", + expectedResult = 1) + self.assert_re_match("Usage", out[1]) + + for cmd in ["comment", "port", "driver"]: + + out = self.runcmd("smbcontrol self printnotify printer spottyfoot " + "%s" % cmd, expectedResult = 1) + self.assert_re_match("Usage", out[1]) + + out = self.runcmd("smbcontrol self printnotify printer spottyfoot " + "%s value" % cmd) + +class Profile(comfychair.TestCase): + """Test setting the profiling level.""" + def runtest(self): + self.require_root() + out = self.runcmd("smbcontrol self profile", expectedResult = 1) + self.assert_re_match("Usage", out[1]) + + out = self.runcmd("smbcontrol self profile spottyfoot", + expectedResult = 1) + self.assert_re_match("Unknown", out[1]) + + for cmd in ["off", "count", "on", "flush"]: + out = self.runcmd("smbcontrol self profile %s" % cmd) + +class ProfileLevel(comfychair.TestCase): + """Test requesting the current profiling level.""" + def runtest(self): + self.require_root() + out = self.runcmd("smbcontrol self profilelevel spottyfoot", + expectedResult = 1) + self.assert_re_match("Usage", out[1]) + + out = self.runcmd("smbcontrol self profilelevel") + +class TimeoutArg(comfychair.TestCase): + """Test the --timeout argument.""" + def runtest(self): + out = self.runcmd("smbcontrol --timeout 5 self noop") + out = self.runcmd("smbcontrol --timeout spottyfoot self noop", + expectedResult = 1) + +class ConfigFileArg(comfychair.TestCase): + """Test the --configfile argument.""" + def runtest(self): + out = self.runcmd("smbcontrol --configfile /dev/null self noop") + +class BogusArg(comfychair.TestCase): + """Test a bogus command line argument.""" + def runtest(self): + out = self.runcmd("smbcontrol --bogus self noop", expectedResult = 1) + +tests = [NoArgs, OneArg, SmbdDest, NmbdDest, WinbinddDest, PidDest, + SelfDest, BadDest, BadCmd, Debug, ForceElection, SamSync, + SamRepl, DmallocMark, DmallocChanged, Shutdown, DrvUpgrade, + CloseShare, Ping, Debuglevel, PrintNotify, Profile, ProfileLevel, + TimeoutArg, ConfigFileArg, BogusArg] + +# Handle execution of this file as a main program + +if __name__ == '__main__': + comfychair.main(tests) diff --git a/source/stf/spoolss.py b/source/stf/spoolss.py new file mode 100755 index 00000000000..735291508bc --- /dev/null +++ b/source/stf/spoolss.py @@ -0,0 +1,288 @@ +#!/usr/bin/python + +import re +import comfychair, stf +from samba import spoolss + +class PrintServerTest(comfychair.TestCase): + """An abstract class requiring a print server.""" + def setUp(self): + # TODO: create a test printer + self.server = stf.get_server(platform = "nt") + self.require(self.server != None, "print server required") + # TODO: remove hardcoded printer name + self.printername = "p" + self.uncname = "\\\\%s\\%s" % \ + (self.server["hostname"], self.printername) + +class W2kPrintServerTest(comfychair.TestCase): + """An abstract class requiring a print server.""" + def setUp(self): + # TODO: create a test printer + self.server = stf.get_server(platform = "nt5") + self.require(self.server != None, "print server required") + # TODO: remove hardcoded printer name + self.printername = "p" + self.uncname = "\\\\%s\\%s" % \ + (self.server["hostname"], self.printername) + +class CredentialTest(PrintServerTest): + """An class that calls a function with various sets of credentials.""" + def runTest(self): + + bad_user_creds = {"username": "spotty", + "domain": "dog", + "password": "bone"} + + cases = ((self.server["administrator"], "Admin credentials", 1), + (bad_user_creds, "Bad credentials", 0)) + + # TODO: add unpriv user case + + for creds, testname, result in cases: + try: + self.runTestArg(creds) + except: + if result: + import traceback + traceback.print_exc() + self.fail("rpc with creds %s failed when it " + "should have suceeded" % creds) + return + + if not result: + self.fail("rpc with creds %s suceeded when it should " + "have failed" % creds) + +class ArgTestServer(PrintServerTest): + """Test a RPC that takes a UNC print server name.""" + def runTest(self): + + # List of test cases, %s substituted for server name + + cases = (("", "No server name", 0), + ("\\\\%s", "Valid server name", 1), + ("\\%s", "Invalid unc server name", 0), + ("\\\\%s__", "Invalid unc server name", 0)) + + for unc, testname, result in cases: + unc = re.sub("%s", self.server["hostname"], unc) + try: + self.runTestArg(unc) + except: + if result: + self.fail("rpc(\"%s\") failed when it should have " + "suceeded" % unc) + return + + if not result: + # Suceeded when we should have failed + self.fail("rpc(\"%s\") suceeded when it should have " + "failed" % unc) + +class ArgTestServerAndPrinter(ArgTestServer): + """Test a RPC that takes a UNC print server or UNC printer name.""" + def runTest(self): + + ArgTestServer.runTest(self) + + # List of test cases, %s substituted for server name, %p substituted + # for printer name. + + cases = (("\\\\%s\\%p", "Valid server and printer name", 1), + ("\\\\%s\\%p__", "Valid server, invalid printer name", 0), + ("\\\\%s__\\%p", "Invalid server, valid printer name", 0)) + + for unc, testname, result in cases: + unc = re.sub("%s", self.server["hostname"], unc) + unc = re.sub("%p", self.printername, unc) + try: + self.runTestArg(unc) + except: + if result: + self.fail("openprinter(\"%s\") failed when it should have " + "suceeded" % unc) + return + + if not result: + # Suceeded when we should have failed + self.fail("openprinter(\"%s\") suceeded when it should have " + "failed" % unc) + +class OpenPrinterArg(ArgTestServerAndPrinter): + """Test the OpenPrinter RPC with combinations of valid and invalid + server and printer names.""" + def runTestArg(self, unc): + spoolss.openprinter(unc) + +class OpenPrinterCred(CredentialTest): + """Test opening printer with good and bad credentials.""" + def runTestArg(self, creds): + spoolss.openprinter(self.uncname, creds = creds) + +class ClosePrinter(PrintServerTest): + """Test the ClosePrinter RPC on a printer handle.""" + def runTest(self): + hnd = spoolss.openprinter(self.uncname) + spoolss.closeprinter(hnd) + +class ClosePrinterServer(PrintServerTest): + """Test the ClosePrinter RPC on a print server handle.""" + def runTest(self): + hnd = spoolss.openprinter("\\\\%s" % self.server["hostname"]) + spoolss.closeprinter(hnd) + +class GetPrinterInfo(PrintServerTest): + """Retrieve printer info at various levels.""" + + # Sample printer data + + sample_info = { + 0: {'printer_errors': 0, 'unknown18': 0, 'unknown13': 0, 'unknown26': 0, 'cjobs': 0, 'unknown11': 0, 'server_name': '\\\\win2kdc1', 'total_pages': 0, 'unknown15': 586, 'unknown16': 0, 'month': 2, 'unknown20': 0, 'second': 23, 'unknown22': 983040, 'unknown25': 0, 'total_bytes': 0, 'unknown27': 0, 'year': 2003, 'build_version': 2195, 'unknown28': 0, 'global_counter': 4, 'day': 13, 'minute': 53, 'total_jobs': 0, 'unknown29': 1114112, 'name': '\\\\win2kdc1\\p', 'hour': 2, 'level': 0, 'c_setprinter': 0, 'change_id': 522454169, 'major_version': 5, 'unknown23': 15, 'day_of_week': 4, 'unknown14': 1, 'session_counter': 2, 'status': 1, 'unknown7': 1, 'unknown8': 0, 'unknown9': 0, 'milliseconds': 421, 'unknown24': 0}, + 1: {'comment': "I'm a teapot!", 'level': 1, 'flags': 8388608, 'name': '\\\\win2kdc1\\p', 'description': '\\\\win2kdc1\\p,HP LaserJet 4,Canberra office'}, + 2: {'comment': "I'm a teapot!", 'status': 1, 'print_processor': 'WinPrint', 'until_time': 0, 'share_name': 'p', 'start_time': 0, 'device_mode': {'icm_method': 1, 'bits_per_pel': 0, 'log_pixels': 0, 'orientation': 1, 'panning_width': 0, 'color': 2, 'pels_width': 0, 'print_quality': 600, 'driver_version': 24, 'display_flags': 0, 'y_resolution': 600, 'media_type': 0, 'display_frequency': 0, 'icm_intent': 0, 'pels_height': 0, 'reserved1': 0, 'size': 220, 'scale': 100, 'dither_type': 0, 'panning_height': 0, 'default_source': 7, 'duplex': 1, 'fields': 16131, 'spec_version': 1025, 'copies': 1, 'device_name': '\\\\win2kdc1\\p', 'paper_size': 1, 'paper_length': 0, 'private': 'private', 'collate': 0, 'paper_width': 0, 'form_name': 'Letter', 'reserved2': 0, 'tt_option': 0}, 'port_name': 'LPT1:', 'sepfile': '', 'parameters': '', 'security_descriptor': {'group_sid': 'S-1-5-21-1606980848-1677128483-854245398-513', 'sacl': None, 'dacl': {'ace_list': [{'flags': 0, 'type': 0, 'mask': 983052, 'trustee': 'S-1-5-32-544'}, {'flags': 9, 'type': 0, 'mask': 983056, 'trustee': 'S-1-5-32-544'}, {'flags': 0, 'type': 0, 'mask': 131080, 'trustee': 'S-1-5-21-1606980848-1677128483-854245398-1121'}, {'flags': 10, 'type': 0, 'mask': 131072, 'trustee': 'S-1-3-0'}, {'flags': 9, 'type': 0, 'mask': 983056, 'trustee': 'S-1-3-0'}, {'flags': 0, 'type': 0, 'mask': 131080, 'trustee': 'S-1-5-21-1606980848-1677128483-854245398-1124'}, {'flags': 0, 'type': 0, 'mask': 131080, 'trustee': 'S-1-1-0'}, {'flags': 0, 'type': 0, 'mask': 983052, 'trustee': 'S-1-5-32-550'}, {'flags': 9, 'type': 0, 'mask': 983056, 'trustee': 'S-1-5-32-550'}, {'flags': 0, 'type': 0, 'mask': 983052, 'trustee': 'S-1-5-32-549'}, {'flags': 9, 'type': 0, 'mask': 983056, 'trustee': 'S-1-5-32-549'}, {'flags': 0, 'type': 0, 'mask': 983052, 'trustee': 'S-1-5-21-1606980848-1677128483-854245398-1106'}], 'revision': 2}, 'owner_sid': 'S-1-5-32-544', 'revision': 1}, 'name': '\\\\win2kdc1\\p', 'server_name': '\\\\win2kdc1', 'level': 2, 'datatype': 'RAW', 'cjobs': 0, 'average_ppm': 0, 'priority': 1, 'driver_name': 'HP LaserJet 4', 'location': 'Canberra office', 'attributes': 8776, 'default_priority': 0}, + 3: {'flags': 4, 'security_descriptor': {'group_sid': 'S-1-5-21-1606980848-1677128483-854245398-513', 'sacl': None, 'dacl': {'ace_list': [{'flags': 0, 'type': 0, 'mask': 983052, 'trustee': 'S-1-5-32-544'}, {'flags': 9, 'type': 0, 'mask': 983056, 'trustee': 'S-1-5-32-544'}, {'flags': 0, 'type': 0, 'mask': 131080, 'trustee': 'S-1-5-21-1606980848-1677128483-854245398-1121'}, {'flags': 10, 'type': 0, 'mask': 131072, 'trustee': 'S-1-3-0'}, {'flags': 9, 'type': 0, 'mask': 983056, 'trustee': 'S-1-3-0'}, {'flags': 0, 'type': 0, 'mask': 131080, 'trustee': 'S-1-5-21-1606980848-1677128483-854245398-1124'}, {'flags': 0, 'type': 0, 'mask': 131080, 'trustee': 'S-1-1-0'}, {'flags': 0, 'type': 0, 'mask': 983052, 'trustee': 'S-1-5-32-550'}, {'flags': 9, 'type': 0, 'mask': 983056, 'trustee': 'S-1-5-32-550'}, {'flags': 0, 'type': 0, 'mask': 983052, 'trustee': 'S-1-5-32-549'}, {'flags': 9, 'type': 0, 'mask': 983056, 'trustee': 'S-1-5-32-549'}, {'flags': 0, 'type': 0, 'mask': 983052, 'trustee': 'S-1-5-21-1606980848-1677128483-854245398-1106'}], 'revision': 2}, 'owner_sid': 'S-1-5-32-544', 'revision': 1}, 'level': 3} + } + + def runTest(self): + self.hnd = spoolss.openprinter(self.uncname) + + # Everyone should have getprinter levels 0-3 + + for i in (0, 1, 2, 3): + info = self.hnd.getprinter(level = i) + try: + stf.dict_check(self.sample_info[i], info) + except ValueError, msg: + raise "info%d: %s" % (i, msg) + +class EnumPrinters(PrintServerTest): + """Enumerate print info at various levels.""" + + sample_info = { + + 0: {'q': {'printer_errors': 0, 'unknown18': 0, 'unknown13': 0, 'unknown26': 0, 'cjobs': 0, 'unknown11': 0, 'server_name': '', 'total_pages': 0, 'unknown15': 586, 'unknown16': 0, 'month': 2, 'unknown20': 0, 'second': 23, 'unknown22': 983040, 'unknown25': 0, 'total_bytes': 0, 'unknown27': 0, 'year': 2003, 'build_version': 2195, 'unknown28': 0, 'global_counter': 4, 'day': 13, 'minute': 53, 'total_jobs': 0, 'unknown29': -1833435136, 'name': 'q', 'hour': 2, 'level': 0, 'c_setprinter': 0, 'change_id': 522454169, 'major_version': 5, 'unknown23': 15, 'day_of_week': 4, 'unknown14': 1, 'session_counter': 1, 'status': 0, 'unknown7': 1, 'unknown8': 0, 'unknown9': 0, 'milliseconds': 421, 'unknown24': 0}, 'p': {'printer_errors': 0, 'unknown18': 0, 'unknown13': 0, 'unknown26': 0, 'cjobs': 0, 'unknown11': 0, 'server_name': '', 'total_pages': 0, 'unknown15': 586, 'unknown16': 0, 'month': 2, 'unknown20': 0, 'second': 23, 'unknown22': 983040, 'unknown25': 0, 'total_bytes': 0, 'unknown27': 0, 'year': 2003, 'build_version': 2195, 'unknown28': 0, 'global_counter': 4, 'day': 13, 'minute': 53, 'total_jobs': 0, 'unknown29': -1831337984, 'name': 'p', 'hour': 2, 'level': 0, 'c_setprinter': 0, 'change_id': 522454169, 'major_version': 5, 'unknown23': 15, 'day_of_week': 4, 'unknown14': 1, 'session_counter': 1, 'status': 1, 'unknown7': 1, 'unknown8': 0, 'unknown9': 0, 'milliseconds': 421, 'unknown24': 0}, 'magpie': {'printer_errors': 0, 'unknown18': 0, 'unknown13': 0, 'unknown26': 0, 'cjobs': 0, 'unknown11': 0, 'server_name': '', 'total_pages': 0, 'unknown15': 586, 'unknown16': 0, 'month': 2, 'unknown20': 0, 'second': 23, 'unknown22': 983040, 'unknown25': 0, 'total_bytes': 0, 'unknown27': 0, 'year': 2003, 'build_version': 2195, 'unknown28': 0, 'global_counter': 4, 'day': 13, 'minute': 53, 'total_jobs': 0, 'unknown29': 1114112, 'name': 'magpie', 'hour': 2, 'level': 0, 'c_setprinter': 0, 'change_id': 522454169, 'major_version': 5, 'unknown23': 15, 'day_of_week': 4, 'unknown14': 1, 'session_counter': 1, 'status': 0, 'unknown7': 1, 'unknown8': 0, 'unknown9': 0, 'milliseconds': 421, 'unknown24': 0}}, + + 1: {'q': {'comment': 'cheepy birds', 'level': 1, 'flags': 8388608, 'name': 'q', 'description': 'q,HP LaserJet 4,'}, 'p': {'comment': "I'm a teapot!", 'level': 1, 'flags': 8388608, 'name': 'p', 'description': 'p,HP LaserJet 4,Canberra office'}, 'magpie': {'comment': '', 'level': 1, 'flags': 8388608, 'name': 'magpie', 'description': 'magpie,Generic / Text Only,'}} + } + + def runTest(self): + for i in (0, 1): + info = spoolss.enumprinters( + "\\\\%s" % self.server["hostname"], level = i) + try: + stf.dict_check(self.sample_info[i], info) + except ValueError, msg: + raise "info%d: %s" % (i, msg) + +class EnumPrintersArg(ArgTestServer): + def runTestArg(self, unc): + spoolss.enumprinters(unc) + +class EnumPrintersCred(CredentialTest): + """Test opening printer with good and bad credentials.""" + def runTestArg(self, creds): + spoolss.enumprinters( + "\\\\%s" % self.server["hostname"], creds = creds) + +class EnumPrinterdrivers(PrintServerTest): + + sample_info = { + 1: {'Okipage 10ex (PCL5E) : STANDARD': {'name': 'Okipage 10ex (PCL5E) : STANDARD', 'level': 1}, 'Generic / Text Only': {'name': 'Generic / Text Only', 'level': 1}, 'Brother HL-1030 series': {'name': 'Brother HL-1030 series', 'level': 1}, 'Brother HL-1240 series': {'name': 'Brother HL-1240 series', 'level': 1}, 'HP DeskJet 1220C Printer': {'name': 'HP DeskJet 1220C Printer', 'level': 1}, 'HP LaserJet 4100 PCL 6': {'name': 'HP LaserJet 4100 PCL 6', 'level': 1}, 'HP LaserJet 4': {'name': 'HP LaserJet 4', 'level': 1}}, + 2: {'Okipage 10ex (PCL5E) : STANDARD': {'version': 2, 'config_file': '\\\\WIN2KDC1\\print$\\W32X86\\2\\RASDDUI.DLL', 'name': 'Okipage 10ex (PCL5E) : STANDARD', 'driver_path': '\\\\WIN2KDC1\\print$\\W32X86\\2\\RASDD.DLL', 'data_file': '\\\\WIN2KDC1\\print$\\W32X86\\2\\OKIPAGE.DLL', 'level': 2, 'architecture': 'Windows NT x86'}, 'Generic / Text Only': {'version': 3, 'config_file': '\\\\WIN2KDC1\\print$\\W32X86\\3\\UNIDRVUI.DLL', 'name': 'Generic / Text Only', 'driver_path': '\\\\WIN2KDC1\\print$\\W32X86\\3\\UNIDRV.DLL', 'data_file': '\\\\WIN2KDC1\\print$\\W32X86\\3\\TTY.GPD', 'level': 2, 'architecture': 'Windows NT x86'}, 'Brother HL-1030 series': {'version': 3, 'config_file': '\\\\WIN2KDC1\\print$\\W32X86\\3\\BRUHL99A.DLL', 'name': 'Brother HL-1030 series', 'driver_path': '\\\\WIN2KDC1\\print$\\W32X86\\3\\BROHL99A.DLL', 'data_file': '\\\\WIN2KDC1\\print$\\W32X86\\3\\BROHL103.PPD', 'level': 2, 'architecture': 'Windows NT x86'}, 'Brother HL-1240 series': {'version': 3, 'config_file': '\\\\WIN2KDC1\\print$\\W32X86\\3\\BRUHL99A.DLL', 'name': 'Brother HL-1240 series', 'driver_path': '\\\\WIN2KDC1\\print$\\W32X86\\3\\BROHL99A.DLL', 'data_file': '\\\\WIN2KDC1\\print$\\W32X86\\3\\BROHL124.PPD', 'level': 2, 'architecture': 'Windows NT x86'}, 'HP DeskJet 1220C Printer': {'version': 3, 'config_file': '\\\\WIN2KDC1\\print$\\W32X86\\3\\HPW8KMD.DLL', 'name': 'HP DeskJet 1220C Printer', 'driver_path': '\\\\WIN2KDC1\\print$\\W32X86\\3\\HPW8KMD.DLL', 'data_file': '\\\\WIN2KDC1\\print$\\W32X86\\3\\HPW8KMD.DLL', 'level': 2, 'architecture': 'Windows NT x86'}, 'HP LaserJet 4100 PCL 6': {'version': 3, 'config_file': '\\\\WIN2KDC1\\print$\\W32X86\\3\\HPBF042E.DLL', 'name': 'HP LaserJet 4100 PCL 6', 'driver_path': '\\\\WIN2KDC1\\print$\\W32X86\\3\\HPBF042G.DLL', 'data_file': '\\\\WIN2KDC1\\print$\\W32X86\\3\\HPBF042I.PMD', 'level': 2, 'architecture': 'Windows NT x86'}, 'HP LaserJet 4': {'version': 2, 'config_file': '\\\\WIN2KDC1\\print$\\W32X86\\2\\hpblff0.dll', 'name': 'HP LaserJet 4', 'driver_path': '\\\\WIN2KDC1\\print$\\W32X86\\2\\hpblff2.dll', 'data_file': '\\\\WIN2KDC1\\print$\\W32X86\\2\\hpblff39.pmd', 'level': 2, 'architecture': 'Windows NT x86'}} + } + + def runTest(self): + for i in (1, 2): + info = spoolss.enumprinterdrivers( + "\\\\%s" % self.server["hostname"], level = i) + try: + if not self.sample_info.has_key(i): + self.log("%s" % info) + self.fail() + stf.dict_check(self.sample_info[i], info) + except ValueError, msg: + raise "info%d: %s" % (i, msg) + +class EnumPrinterdriversArg(ArgTestServer): + def runTestArg(self, unc): + spoolss.enumprinterdrivers(unc) + +class EnumPrinterdriversCred(CredentialTest): + """Test opening printer with good and bad credentials.""" + def runTestArg(self, creds): + spoolss.enumprinterdrivers( + "\\\\%s" % self.server["hostname"], creds = creds) + +def usage(): + print "Usage: spoolss.py [options] [test1[,test2...]]" + print "\t -v/--verbose Display debugging information" + print "\t -l/--list-tests List available tests" + print + print "A list of comma separated test names or regular expressions" + print "can be used to filter the tests performed." + +def test_match(subtest_list, test_name): + """Return true if a test matches a comma separated list of regular + expression of test names.""" + # re.match does an implicit ^ at the start of the pattern. + # Explicitly anchor to end to avoid matching substrings. + for s in string.split(subtest_list, ","): + if re.match(s + "$", test_name): + return 1 + return 0 + +if __name__ == "__main__": + import os, sys, string + import getopt + + try: + opts, args = getopt.getopt(sys.argv[1:], "vl", \ + ["verbose", "list-tests"]) + except getopt.GetoptError: + usage() + sys.exit(0) + + verbose = 0 + list_tests = 0 + + for opt, arg in opts: + if opt in ("-v", "--verbose"): + verbose = 1 + if opt in ("-l", "--list-tests"): + list_tests = 1 + + if len(args) > 1: + usage() + sys.exit(0) + + test_list = [ + OpenPrinterArg, + OpenPrinterCred, + ClosePrinter, + ClosePrinterServer, + GetPrinterInfo, + EnumPrinters, + EnumPrintersCred, + EnumPrintersArg, + EnumPrinterdrivers, + EnumPrinterdriversCred, + EnumPrinterdriversArg, + ] + + if len(args): + t = [] + for test in test_list: + if test_match(args[0], test.__name__): + t.append(test) + test_list = t + + if os.environ.has_key("SAMBA_DEBUG"): + spoolss.setup_logging(interactive = 1) + spoolss.set_debuglevel(10) + + if list_tests: + for test in test_list: + print test.__name__ + else: + comfychair.runtests(test_list, verbose = verbose) diff --git a/source/stf/standardcheck.py b/source/stf/standardcheck.py new file mode 100644 index 00000000000..e3292353e8b --- /dev/null +++ b/source/stf/standardcheck.py @@ -0,0 +1,34 @@ +#! /usr/bin/python + +# Comfychair test cases for Samba + +# Copyright (C) 2003 by Martin Pool <mbp@samba.org> +# +# This program 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 2 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; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + +"""These tests are run by Samba's "make check".""" + +import strings, comfychair +import smbcontrol, sambalib + +# There should not be any actual tests in here: this file just serves +# to define the ones run by default. They're imported from other +# modules. + +tests = strings.tests + smbcontrol.tests + sambalib.tests + +if __name__ == '__main__': + comfychair.main(tests) diff --git a/source/stf/stf.py b/source/stf/stf.py new file mode 100755 index 00000000000..ee0ff735612 --- /dev/null +++ b/source/stf/stf.py @@ -0,0 +1,101 @@ +#!/usr/bin/python +# +# Samba Testing Framework for Unit-testing +# + +import os, string, re +import osver + +def get_server_list_from_string(s): + + server_list = [] + + # Format is a list of server:domain\username%password separated + # by commas. + + for entry in string.split(s, ","): + + # Parse entry + + m = re.match("(.*):(.*)(\\\\|/)(.*)%(.*)", entry) + if not m: + raise "badly formed server list entry '%s'" % entry + + server = m.group(1) + domain = m.group(2) + username = m.group(4) + password = m.group(5) + + # Categorise servers + + server_list.append({"platform": osver.os_version(server), + "hostname": server, + "administrator": {"username": username, + "domain": domain, + "password" : password}}) + + return server_list + +def get_server_list(): + """Iterate through all sources of server info and append them all + in one big list.""" + + server_list = [] + + # The $STF_SERVERS environment variable + + if os.environ.has_key("STF_SERVERS"): + server_list = server_list + \ + get_server_list_from_string(os.environ["STF_SERVERS"]) + + return server_list + +def get_server(platform = None): + """Return configuration information for a server. The platform + argument can be a string either 'nt4' or 'nt5' for Windows NT or + Windows 2000 servers, or just 'nt' for Windows NT and higher.""" + + server_list = get_server_list() + + for server in server_list: + if platform: + p = server["platform"] + if platform == "nt": + if (p == osver.PLATFORM_NT4 or p == osver.PLATFORM_NT5): + return server + if platform == "nt4" and p == osver.PLATFORM_NT4: + return server + if platform == "nt5" and p == osver.PLATFORM_NT5: + return server + else: + # No filter defined, return first in list + return server + + return None + +def dict_check(sample_dict, real_dict): + """Check that real_dict contains all the keys present in sample_dict + and no extras. Also check that common keys are of them same type.""" + tmp = real_dict.copy() + for key in sample_dict.keys(): + # Check existing key and type + if not real_dict.has_key(key): + raise ValueError, "dict does not contain key '%s'" % key + if type(sample_dict[key]) != type(real_dict[key]): + raise ValueError, "dict has differing types (%s vs %s) for key " \ + "'%s'" % (type(sample_dict[key]), type(real_dict[key]), key) + # Check dictionaries recursively + if type(sample_dict[key]) == dict: + dict_check(sample_dict[key], real_dict[key]) + # Delete visited keys from copy + del(tmp[key]) + # Any keys leftover are present in the real dict but not the sample + if len(tmp) == 0: + return + result = "dict has extra keys: " + for key in tmp.keys(): + result = result + key + " " + raise ValueError, result + +if __name__ == "__main__": + print get_server(platform = "nt") diff --git a/source/stf/strings.py b/source/stf/strings.py new file mode 100755 index 00000000000..86f7acdeb47 --- /dev/null +++ b/source/stf/strings.py @@ -0,0 +1,151 @@ +#! /usr/bin/python + +# Comfychair test cases for Samba string functions. + +# Copyright (C) 2003 by Martin Pool <mbp@samba.org> +# +# This program 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 2 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; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + +# XXX: All this code assumes that the Unix character set is UTF-8, +# which is the most common setting. I guess it would be better to +# force it to that value while running the tests. I'm not sure of the +# best way to do that yet. +# +# Note that this is NOT the case in C code until the loadparm table is +# intialized -- the default seems to be ASCII, which rather lets Samba +# off the hook. :-) The best way seems to be to put this in the test +# harnesses: +# +# lp_load("/dev/null", True, False, False); +# +# -- mbp + +import sys, re, comfychair +from unicodenames import * + +def signum(a): + if a < 0: + return -1 + elif a > 0: + return +1 + else: + return 0 + + +class PushUCS2_Tests(comfychair.TestCase): + """Conversion to/from UCS2""" + def runtest(self): + OE = LATIN_CAPITAL_LETTER_O_WITH_DIARESIS + oe = LATIN_CAPITAL_LETTER_O_WITH_DIARESIS + cases = ['hello', + 'hello world', + 'g' + OE + OE + 'gomobile', + 'g' + OE + oe + 'gomobile', + u'foo\u0100', + KATAKANA_LETTER_A * 20, + ] + for u8str in cases: + out, err = self.runcmd("t_push_ucs2 \"%s\"" % u8str.encode('utf-8')) + self.assert_equal(out, "0\n") + + +class StrCaseCmp(comfychair.TestCase): + """String comparisons in simple ASCII""" + def run_strcmp(self, a, b, expect): + out, err = self.runcmd('t_strcmp \"%s\" \"%s\"' % (a.encode('utf-8'), b.encode('utf-8'))) + if signum(int(out)) != expect: + self.fail("comparison failed:\n" + " a=%s\n" + " b=%s\n" + " expected=%s\n" + " result=%s\n" % (`a`, `b`, `expect`, `out`)) + + def runtest(self): + # A, B, strcasecmp(A, B) + cases = [('hello', 'hello', 0), + ('hello', 'goodbye', +1), + ('goodbye', 'hello', -1), + ('hell', 'hello', -1), + ('', '', 0), + ('a', '', +1), + ('', 'a', -1), + ('a', 'A', 0), + ('aa', 'aA', 0), + ('Aa', 'aa', 0), + ('longstring ' * 100, 'longstring ' * 100, 0), + ('longstring ' * 100, 'longstring ' * 100 + 'a', -1), + ('longstring ' * 100 + 'a', 'longstring ' * 100, +1), + (KATAKANA_LETTER_A, KATAKANA_LETTER_A, 0), + (KATAKANA_LETTER_A, 'a', 1), + ] + for a, b, expect in cases: + self.run_strcmp(a, b, expect) + +class strstr_m(comfychair.TestCase): + """String comparisons in simple ASCII""" + def run_strstr(self, a, b, expect): + out, err = self.runcmd('t_strstr \"%s\" \"%s\"' % (a.encode('utf-8'), b.encode('utf-8'))) + if (out != (expect + '\n').encode('utf-8')): + self.fail("comparison failed:\n" + " a=%s\n" + " b=%s\n" + " expected=%s\n" + " result=%s\n" % (`a`, `b`, `expect+'\n'`, `out`)) + + def runtest(self): + # A, B, strstr_m(A, B) + cases = [('hello', 'hello', 'hello'), + ('hello', 'goodbye', '(null)'), + ('goodbye', 'hello', '(null)'), + ('hell', 'hello', '(null)'), + ('hello', 'hell', 'hello'), + ('', '', ''), + ('a', '', 'a'), + ('', 'a', '(null)'), + ('a', 'A', '(null)'), + ('aa', 'aA', '(null)'), + ('Aa', 'aa', '(null)'), + ('%v foo', '%v', '%v foo'), + ('foo %v foo', '%v', '%v foo'), + ('foo %v', '%v', '%v'), + ('longstring ' * 100, 'longstring ' * 99, 'longstring ' * 100), + ('longstring ' * 99, 'longstring ' * 100, '(null)'), + ('longstring a' * 99, 'longstring ' * 100 + 'a', '(null)'), + ('longstring ' * 100 + 'a', 'longstring ' * 100, 'longstring ' * 100 + 'a'), + (KATAKANA_LETTER_A, KATAKANA_LETTER_A + 'bcd', '(null)'), + (KATAKANA_LETTER_A + 'bcde', KATAKANA_LETTER_A + 'bcd', KATAKANA_LETTER_A + 'bcde'), + ('d'+KATAKANA_LETTER_A + 'bcd', KATAKANA_LETTER_A + 'bcd', KATAKANA_LETTER_A + 'bcd'), + ('d'+KATAKANA_LETTER_A + 'bd', KATAKANA_LETTER_A + 'bcd', '(null)'), + + ('e'+KATAKANA_LETTER_A + 'bcdf', KATAKANA_LETTER_A + 'bcd', KATAKANA_LETTER_A + 'bcdf'), + (KATAKANA_LETTER_A, KATAKANA_LETTER_A + 'bcd', '(null)'), + (KATAKANA_LETTER_A*3, 'a', '(null)'), + ] + for a, b, expect in cases: + self.run_strstr(a, b, expect) + +# Define the tests exported by this module +tests = [StrCaseCmp, + strstr_m, + PushUCS2_Tests] + +# Handle execution of this file as a main program +if __name__ == '__main__': + comfychair.main(tests) + +# Local variables: +# coding: utf-8 +# End: diff --git a/source/stf/test.py b/source/stf/test.py new file mode 100755 index 00000000000..fb57926cc3a --- /dev/null +++ b/source/stf/test.py @@ -0,0 +1,33 @@ +#!/usr/bin/python + +# meta-test-case / example for comfychair. Should demonstrate +# different kinds of failure. + +import comfychair + +class NormalTest(comfychair.TestCase): + def runtest(self): + pass + +class RootTest(comfychair.TestCase): + def setup(self): + self.require_root() + + def runTest(self): + pass + +class GoodExecTest(comfychair.TestCase): + def runtest(self): + stdout = self.runcmd("ls -l") + +class BadExecTest(comfychair.TestCase): + def setup(self): + exit, stdout = self.runcmd_unchecked("spottyfoot --slobber", + skip_on_noexec = 1) + + +tests = [NormalTest, RootTest, GoodExecTest, BadExecTest] + +if __name__ == '__main__': + comfychair.main(tests) + diff --git a/source/stf/unicodenames.py b/source/stf/unicodenames.py new file mode 100644 index 00000000000..d4100cb7f90 --- /dev/null +++ b/source/stf/unicodenames.py @@ -0,0 +1,33 @@ +#! /usr/bin/python + +# Copyright (C) 2003 by Martin Pool <mbp@samba.org> +# +# This program 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 2 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; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + + +""" +Defines symbolic names for a few UNICODE characters, to make test +source code more readable on machines that don't have all the +necessary fonts. + +You can do "import *" on this file safely. +""" + +LATIN_CAPITAL_LETTER_N_WITH_TILDE = u'\u004e' +LATIN_CAPITAL_LETTER_O_WITH_DIARESIS = u'\u00d6' +LATIN_SMALL_LETTER_O_WITH_DIARESIS = u'\u00f6' + +KATAKANA_LETTER_A = u'\u30a2' |