regress-builder 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. #!/usr/bin/env python3
  2. #
  3. # This file is Copyright 2010 by the GPSD project
  4. # SPDX-License-Identifier: BSD-2-clause
  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 sys
  27. import time
  28. driver_names = (
  29. "aivdm",
  30. "ashtech",
  31. # "earthmate",
  32. "evermore",
  33. "fv18",
  34. "garmin",
  35. "garmintxt",
  36. "geostar",
  37. "gpsclock",
  38. "itrax",
  39. "mtk3301",
  40. "navcom",
  41. "nmea",
  42. "ntrip",
  43. "oncore",
  44. "oceanserver",
  45. "rtcm104v2",
  46. # "rtcm104v3",
  47. "sirf",
  48. "superstar2",
  49. # "tnt",
  50. "tripmate",
  51. "tsip",
  52. "ubx",
  53. "timing",
  54. "clientdebug",
  55. "oldstyle",
  56. "ntpshm",
  57. "pps",
  58. "reconfigure",
  59. "controlsend",
  60. )
  61. cyclesize = 2 ** len(driver_names)
  62. def Sequential():
  63. "Generate sequential test vectors for exhautive search."
  64. for i in xrange(cyclesize):
  65. yield i
  66. return
  67. def MonteCarlo(seed):
  68. "Generate a random shuffle of test vectors for Monte Carlo testing."
  69. # All the magic here is in the choice of modulus. Any odd number will
  70. # be relatively prime to any power of two and thus be sufficient to step
  71. # around the cycle hitting each number exactly once. We'd like adjacent
  72. # stops to have a large distance from each other. Number theory says the
  73. # best way to do this is to choose a modulus close to cyclesize / phi,
  74. # where phi is the golden ratio (1 + 5**0.5)/2.
  75. modulus = int(cyclesize / 1.618033980)
  76. if modulus % 2 == 0:
  77. modulus += 1
  78. for i in xrange(cyclesize):
  79. yield (seed + i * modulus) % cyclesize
  80. return
  81. class TestFactory:
  82. "Manage compilation tests."
  83. Preamble = '''
  84. env X_LIBS="" \\
  85. CPPFLAGS="-I/usr/local/include " \\
  86. LDFLAGS=" -L/usr/local/lib -g" \\
  87. CFLAGS="-g -O2 -W -Wall" \\
  88. ./configure --prefix=/home/gpsd --disable-shared \\
  89. '''
  90. def n2v(self, n):
  91. "Number to test-vector"
  92. v = [0] * len(driver_names)
  93. i = 0
  94. while n > 0:
  95. v[i] = n % 2
  96. i += 1
  97. n = n >> 1
  98. return v
  99. def test_header(self, vector):
  100. hdr = ""
  101. for (i, name) in enumerate(driver_names):
  102. hdr += "--" + ("disable", "enable")[vector[i]] + "-" + name + " "
  103. return hdr[:-1]
  104. def make(self, vector):
  105. if os.system("make 2>&1 > /dev/null"):
  106. print("FAILED: ", self.test_header(vector))
  107. else:
  108. print("SUCCEEDED:", self.test_header(vector))
  109. def configure_build(self, vector):
  110. test = TestFactory.Preamble
  111. for (i, name) in enumerate(driver_names):
  112. test += (" --" + ("disable", "enable")[vector[i]] +
  113. "-" + name + " \\\n")
  114. test += "\n"
  115. if os.system("(" + test + ") >/dev/null"):
  116. print("configure FAILED:", self.test_header(vector))
  117. else:
  118. self.make(vector)
  119. def filter(self, vector):
  120. "Tell us whether this combination needs to be tested."
  121. # No drivers at all won't even configure
  122. if vector == [0] * len(driver_names):
  123. return False
  124. compiled_in = map(lambda e: e[1], filter(lambda e: e[0],
  125. zip(vector, driver_names)))
  126. def on(name):
  127. return name in compiled_in
  128. def off(name):
  129. return name not in compiled_in
  130. # START OF FILTERS
  131. #
  132. # Some types require NMEA support and shouldn't be built without it.
  133. if off("nmea") and (on("fv18") or on("earthmate") or on("tripmate")):
  134. return False
  135. # Earthmate, FV-18 and TripMate code scopes don't overlap.
  136. if on("fv18") + on("earthmate") + on("tripmate") not in (0, 3):
  137. return False
  138. # SiRF has no overlapping code scopes with the DLE-led protocols
  139. # (TSIP, Garmin, Evermore) but the DLE-led protocols have
  140. # overlaps in the packet sniffer.
  141. if on("sirf") != (on("tsip") or on("garmin") or on("evermore")):
  142. return False
  143. #
  144. # END OF FILTERS
  145. return True
  146. def run(self, generator, limit=cyclesize):
  147. expect = 0
  148. for i in range(cyclesize):
  149. if self.filter(self.n2v(i)):
  150. expect += 1
  151. print("# Expect %d of %d tests." % (expect, cyclesize))
  152. starttime = time.time()
  153. included = 0
  154. excluded = 0
  155. for n in generator:
  156. if limit == 0:
  157. return
  158. vector = self.n2v(n)
  159. if not self.filter(vector):
  160. print("EXCLUDED:", self.test_header(vector))
  161. excluded += 1
  162. else:
  163. self.configure_build(vector)
  164. included += 1
  165. limit -= 1
  166. elapsed = int(time.time() - starttime)
  167. hours = elapsed/3600
  168. minutes = (elapsed - (hours * 3600)) / 60
  169. seconds = elapsed - (hours * 3600) - (minutes * 60)
  170. print("%d tests, %d excluded, in %dh%dm%ds" %
  171. (included, excluded, hours, minutes, seconds))
  172. if __name__ == '__main__':
  173. try:
  174. (options, arguments) = getopt.getopt(sys.argv[1:], "n:r:")
  175. except getopt.GetoptError, msg:
  176. sys.exit("regress-builder: " + str(msg))
  177. montecarlo = False
  178. limit = cyclesize
  179. seed = int(time.time())
  180. for (switch, val) in options:
  181. if switch == '-r':
  182. montecarlo = True
  183. limit = int(val)
  184. elif switch == '-s':
  185. montecarlo = True
  186. seed = int(val)
  187. if montecarlo:
  188. print("Monte Carlo test seeded with %d" % seed)
  189. TestFactory().run(MonteCarlo(seed), limit)
  190. else:
  191. print("Sequential compilation test")
  192. TestFactory().run(Sequential())
  193. # vim: set expandtab shiftwidth=4