regress-builder 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. #!/usr/bin/env python
  2. #
  3. # This file is Copyright (c) 2010-2019 by the GPSD project
  4. # BSD terms apply: see the file COPYING in the distribution root for details.
  5. """
  6. usage: regress-builder [-r limit] [-s seed]
  7. Test-compile gpsd with each of every possible combination of device-driver
  8. options, excluding stub drivers we don't support yet. This does a good
  9. job of catching bugs after driver API changes.
  10. The -r option sets Monte Carlo mode (random-walk through configuration space)
  11. and limits the number of tests. The -s, if given, sets a seed; the default is
  12. to seed from the system time.
  13. If no options are given, the script performs a sequential test of all
  14. possibilities. This requires time on the order of 2 ** n where n is
  15. the count of options.
  16. ***IMPORTANT***:
  17. Not only has this program not been updated for Python 3, but it hasn't been
  18. touched in such a long time that it still expects to use make rather than
  19. SCons to do builds (and probably has outdated notions of the drivers as well).
  20. Although updating it for Python 3 doesn't look too difficult, it doesn't make
  21. sense to do so without first restoring it to correct operation with Python 2,
  22. since the former fixes would otherwise be untestable.
  23. """
  24. import getopt
  25. import os
  26. import random
  27. import sys
  28. import time
  29. driver_names = (
  30. "aivdm",
  31. "ashtech",
  32. # "earthmate",
  33. "evermore",
  34. "fv18",
  35. "garmin",
  36. "garmintxt",
  37. "geostar",
  38. "gpsclock",
  39. "itrax",
  40. "mtk3301",
  41. "navcom",
  42. "nmea",
  43. "ntrip",
  44. "oncore",
  45. "oceanserver",
  46. "rtcm104v2",
  47. # "rtcm104v3",
  48. "sirf",
  49. "superstar2",
  50. # "tnt",
  51. "tripmate",
  52. "tsip",
  53. "ubx",
  54. "timing",
  55. "clientdebug",
  56. "oldstyle",
  57. "ntpshm",
  58. "pps",
  59. "reconfigure",
  60. "controlsend",
  61. )
  62. cyclesize = 2 ** len(driver_names)
  63. def Sequential():
  64. "Generate sequential test vectors for exhautive search."
  65. for i in xrange(cyclesize):
  66. yield i
  67. return
  68. def MonteCarlo(seed):
  69. "Generate a random shuffle of test vectors for Monte Carlo testing."
  70. # All the magic here is in the choice of modulus. Any odd number will
  71. # be relatively prime to any power of two and thus be sufficient to step
  72. # around the cycle hitting each number exactly once. We'd like adjacent
  73. # stops to have a large distance from each other. Number theory says the
  74. # best way to do this is to choose a modulus close to cyclesize / phi,
  75. # where phi is the golden ratio (1 + 5**0.5)/2.
  76. modulus = int(cyclesize / 1.618033980)
  77. if modulus % 2 == 0:
  78. modulus += 1
  79. for i in xrange(cyclesize):
  80. yield (seed + i * modulus) % cyclesize
  81. return
  82. class TestFactory:
  83. "Manage compilation tests."
  84. Preamble = '''
  85. env X_LIBS="" \\
  86. CPPFLAGS="-I/usr/local/include " \\
  87. LDFLAGS=" -L/usr/local/lib -g" \\
  88. CFLAGS="-g -O2 -W -Wall" \\
  89. ./configure --prefix=/home/gpsd --disable-shared \\
  90. '''
  91. def n2v(self, n):
  92. "Number to test-vector"
  93. v = [0] * len(driver_names)
  94. i = 0
  95. while n > 0:
  96. v[i] = n % 2
  97. i += 1
  98. n = n >> 1
  99. return v
  100. def test_header(self, vector):
  101. hdr = ""
  102. for (i, name) in enumerate(driver_names):
  103. hdr += "--" + ("disable", "enable")[vector[i]] + "-" + name + " "
  104. return hdr[:-1]
  105. def make(self, vector):
  106. if os.system("make 2>&1 > /dev/null"):
  107. print("FAILED: ", self.test_header(vector))
  108. else:
  109. print("SUCCEEDED:", self.test_header(vector))
  110. def configure_build(self, vector):
  111. test = TestFactory.Preamble
  112. for (i, name) in enumerate(driver_names):
  113. test += (" --" + ("disable", "enable")[vector[i]] +
  114. "-" + name + " \\\n")
  115. test += "\n"
  116. if os.system("(" + test + ") >/dev/null"):
  117. print("configure FAILED:", self.test_header(vector))
  118. else:
  119. self.make(vector)
  120. def filter(self, vector):
  121. "Tell us whether this combination needs to be tested."
  122. # No drivers at all won't even configure
  123. if vector == [0] * len(driver_names):
  124. return False
  125. compiled_in = map(lambda e: e[1], filter(lambda e: e[0],
  126. zip(vector, driver_names)))
  127. def on(name):
  128. return name in compiled_in
  129. def off(name):
  130. return name not in compiled_in
  131. # START OF FILTERS
  132. #
  133. # Some types require NMEA support and shouldn't be built without it.
  134. if off("nmea") and (on("fv18") or on("earthmate") or on("tripmate")):
  135. return False
  136. # Earthmate, FV-18 and TripMate code scopes don't overlap.
  137. if on("fv18") + on("earthmate") + on("tripmate") not in (0, 3):
  138. return False
  139. # SiRF has no overlapping code scopes with the DLE-led protocols
  140. # (TSIP, Garmin, Evermore) but the DLE-led protocols have
  141. # overlaps in the packet sniffer.
  142. if on("sirf") != (on("tsip") or on("garmin") or on("evermore")):
  143. return False
  144. #
  145. # END OF FILTERS
  146. return True
  147. def run(self, generator, limit=cyclesize):
  148. expect = 0
  149. for i in range(cyclesize):
  150. if self.filter(self.n2v(i)):
  151. expect += 1
  152. print("# Expect %d of %d tests." % (expect, cyclesize))
  153. starttime = time.time()
  154. included = 0
  155. excluded = 0
  156. for n in generator:
  157. if limit == 0:
  158. return
  159. vector = self.n2v(n)
  160. if not self.filter(vector):
  161. print("EXCLUDED:", self.test_header(vector))
  162. excluded += 1
  163. else:
  164. self.configure_build(vector)
  165. included += 1
  166. limit -= 1
  167. elapsed = int(time.time() - starttime)
  168. hours = elapsed/3600
  169. minutes = (elapsed - (hours * 3600)) / 60
  170. seconds = elapsed - (hours * 3600) - (minutes * 60)
  171. print("%d tests, %d excluded, in %dh%dm%ds" %
  172. (included, excluded, hours, minutes, seconds))
  173. if __name__ == '__main__':
  174. try:
  175. (options, arguments) = getopt.getopt(sys.argv[1:], "n:r:")
  176. except getopt.GetoptError, msg:
  177. sys.exit("regress-builder: " + str(msg))
  178. montecarlo = False
  179. limit = cyclesize
  180. seed = int(time.time())
  181. for (switch, val) in options:
  182. if switch == '-r':
  183. montecarlo = True
  184. limit = int(val)
  185. elif switch == '-s':
  186. montecarlo = True
  187. seed = int(val)
  188. if montecarlo:
  189. print("Monte Carlo test seeded with %d" % seed)
  190. TestFactory().run(MonteCarlo(seed), limit)
  191. else:
  192. print("Sequential compilation test")
  193. TestFactory().run(Sequential())