summaryrefslogtreecommitdiffstats
path: root/source/stf
diff options
context:
space:
mode:
Diffstat (limited to 'source/stf')
-rw-r--r--source/stf/.cvsignore2
-rw-r--r--source/stf/README.stf3
-rw-r--r--source/stf/comfychair.py445
-rwxr-xr-xsource/stf/example.py38
-rwxr-xr-xsource/stf/info3cache.py54
-rw-r--r--source/stf/notes.txt175
-rwxr-xr-xsource/stf/osver.py55
-rwxr-xr-xsource/stf/pythoncheck.py48
-rw-r--r--source/stf/sambalib.py41
-rwxr-xr-xsource/stf/smbcontrol.py238
-rwxr-xr-xsource/stf/spoolss.py288
-rw-r--r--source/stf/standardcheck.py34
-rwxr-xr-xsource/stf/stf.py101
-rwxr-xr-xsource/stf/strings.py151
-rwxr-xr-xsource/stf/test.py33
-rw-r--r--source/stf/unicodenames.py33
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'