#!/bin/env python """ usage: regress-builder [-r limit] [-s seed] Test-compile gpsd with each of every possible combination of device-driver options, excluding stub drivers we don't support yet. This does a good job of catching bugs after driver API changes. The -r option sets Monte Carlo mode (random-walk through configuration space) and limits the number of tests. The -s, if given, sets a seed; the default is to seed from the system time. If no options are given, the script performs a sequential test of all possibilities. This requires time on the order of 2 ** n where n is the count of options. """ import os, sys, random, getopt, time driver_names = ( "earthmate", "evermore", "fv18", "garmin", "itrax", "navcom", "nmea", "ntrip", "rtcm104", "sirf", # "tnt", "tripmate", "tsip", "ubx", } cyclesize = 2 ** len(driver_names) def Sequential(, "Generate sequential test vectors for exhautive search." for i in xrange(cyclesize): yield i return def MonteCarlo(seed): "Generate a random shuffle of test vectors for Monte Carlo testing." # All the magic here is in the choice of modulus. Any odd number will # be relatively prime to any power of two and thus be sufficient to step # around the cycle hitting each number exactly once. We'd like adjacent # stops to have a large distance from each other. Number theory says the # best way to do this is to choose a modulus close to cyclesize / phi, # where phi is the golden ratio (1 + 5**0.5)/2. modulus = int(cyclesize / 1.618033980) if modulus % 2 == 0: modulus += 1 for i in xrange(cyclesize): yield (seed + i * modulus) % cyclesize return class TestFactory: "Manage compilation tests." Preamble = ''' env X_LIBS="" \\ CPPFLAGS="-I/usr/local/include " \\ LDFLAGS=" -L/usr/local/lib -g" \\ CFLAGS="-g -O2 -W -Wall" \\ ./configure --prefix=/home/gpsd --disable-shared \\ --without-x --enable-python \\ --disable-itrax \\ --disable-italk \\ ''' def n2v(self, n): "Number to test-vector" v = [0] * len(driver_weights.keys()) i = 0 while n > 0: v[i] = n % 2 i += 1 n = n >> 1 return v def test_header(self, vector): hdr = "" for (i, name) in enumerate(driver_names): hdr += "--" + ("disable", "enable")[vector[i]] + "-" + name + " " return hdr[:-1] def make(self, vector): if os.system("make 2>&1 > /dev/null"): print "FAILED: ", self.test_header(vector) else: print "SUCCEEDED:", self.test_header(vector) def configure_build(self, vector): test = TestFactory.Preamble for (i, name) in enumerate(driver_names): test += " --" + ("disable", "enable")[vector[i]] + "-" + name + " \\\n" test += "\n" if os.system("("+ test + ") >/dev/null"): print "configure FAILED:", self.test_header(vector) else: self.make(vector) def filter(self, vector): "Tell us whether this combination needs to be tested." # No drivers at all won't even configure if vector == [0] * len(driver_names): return False compiled_in = map(lambda e: e[1], filter(lambda e: e[0], zip(vector, driver_names))) def on(name): return name in compiled_in def off(name): return name not in compiled_in # START OF FILTERS # # Some types require NMEA support and shouldn't be built without it. if off("nmea") and (on("fv18") or on("earthmate") or on("tripmate")): return False # Earthmate, FV-18 and TripMate code scopes don't overlap. if on("fv18") + on("earthmate") + on("tripmate") not in (0, 3): return False # SiRF has no overlapping code scopes with the DLE-led protocols # (TSIP, Garmin, Evermore) but the DLE-led protocols have # overlaps in the packet sniffer. if on("sirf") != (on("tsip") or on("garmin") or on("evermore")): return False # # END OF FILTERS return True def run(self, generator, limit=cyclesize): expect = 0 for i in range(cyclesize): if self.filter(self.n2v(i)): expect += 1 print "# Expect %d of %d tests." % (expect, cyclesize) starttime = time.time() included = 0 excluded = 0 for n in generator: if limit == 0: return vector = self.n2v(n) if not self.filter(vector): print "EXCLUDED:", self.test_header(vector) excluded += 1 else: self.configure_build(vector) included += 1 limit -= 1 elapsed = int(time.time() - starttime) hours = elapsed/3600 minutes = (elapsed - (hours * 3600)) / 60 seconds = elapsed - (hours * 3600) - (minutes * 60) print "%d tests, %d excluded, in %dh%dm%ds" % (included, excluded, hours, minutes, seconds) if __name__ == '__main__': try: (options, arguments) = getopt.getopt(sys.argv[1:], "n:r:") except getopt.GetoptError, msg: print "regress-builder: " + str(msg) raise SystemExit, 1 montecarlo = False limit = cyclesize seed = int(time.time()) for (switch, val) in options: if switch == '-r': montecarlo = True limit = int(val) elif switch == '-s': montecarlo = True seed = int(val) if montecarlo: print "Monte Carlo test seeded with %d" % seed TestFactory().run(MonteCarlo(seed), limit) else: print "Sequential compilation test" TestFactory().run(Sequential())