unit-test-config.py
Go to the documentation of this file.
1 #!python3
2 
3 # License: Apache 2.0. See LICENSE file in root directory.
4 # Copyright(c) 2020 Intel Corporation. All Rights Reserved.
5 
6 #
7 # Syntax:
8 # unit-test-config.py <dir> <build-dir>
9 #
10 # Looks for possible single-file unit-testing targets (test-*) in $dir, and builds
11 # a CMakeLists.txt in $builddir to compile them.
12 #
13 # Each target is compiled in its own project, so that each file ends up in a different
14 # process and so individual tests cannot affect others except through hardware.
15 #
16 
17 import sys, os, subprocess, locale, re
18 from glob import glob
19 
20 if len(sys.argv) != 3:
21  ourname = os.path.basename(sys.argv[0])
22  print( 'Syntax: ' + ourname + ' <dir> <build-dir>' )
23  print( ' build unit-testing framework for the tree in $dir' )
24  exit(1)
25 dir=sys.argv[1]
26 builddir=sys.argv[2]
27 if not os.path.isdir( dir ):
28  print( 'FATAL Directory not found:', dir )
29  exit(1)
30 if not os.path.isdir( builddir ):
31  print( 'FATAL Directory not found:', builddir )
32  exit(1)
33 
34 have_errors = False
35 
36 def debug(*args):
37  #print( '-D-', *args )
38  pass
39 def error(*args):
40  print( '-E-', *args )
41  global have_errors
42  have_errors = True
43 def filesin( root ):
44  # Yield all files found in root, using relative names ('root/a' would be yielded as 'a')
45  for (path,subdirs,leafs) in os.walk( root ):
46  for leaf in leafs:
47  # We have to stick to Unix conventions because CMake on Windows is fubar...
48  yield os.path.relpath( path + '/' + leaf, root ).replace( '\\', '/' )
49 def find( dir, mask ):
50  pattern = re.compile( mask )
51  for leaf in filesin( dir ):
52  if pattern.search( leaf ):
53  debug(leaf)
54  yield leaf
55 
56 
57 def remove_newlines (lines):
58  for line in lines:
59  if line[-1] == '\n':
60  line = line[:-1] # excluding the endline
61  yield line
62 
63 def grep_( pattern, lines, context ):
64  index = 0
65  matches = 0
66  for line in lines:
67  index = index + 1
68  match = pattern.search( line )
69  if match:
70  context['index'] = index
71  context['line'] = line
72  context['match'] = match
73  yield context
74  matches = matches + 1
75  if matches:
76  del context['index']
77  del context['line']
78  del context['match']
79  # UnicodeDecodeError can be thrown in binary files
80 
81 def grep( expr, *args ):
82  #debug( f"grep {expr} {args}" )
83  pattern = re.compile( expr )
84  context = dict()
85  for filename in args:
86  context['filename'] = filename
87  with open( filename, errors = 'ignore' ) as file:
88  for line in grep_( pattern, remove_newlines( file ), context ):
89  yield context
90 
91 librealsense = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace('\\', '/')
92 src = librealsense + '/src'
93 
94 def generate_cmake( builddir, testdir, testname, filelist ):
95  makefile = builddir + '/' + testdir + '/CMakeLists.txt'
96  debug( ' creating:', makefile )
97  handle = open( makefile, 'w' );
98  filelist = '\n '.join( filelist )
99  handle.write( '''
100 # This file is automatically generated!!
101 # Do not modify or your changes will be lost!
102 
103 cmake_minimum_required( VERSION 3.1.0 )
104 project( ''' + testname + ''' )
105 
106 set( SRC_FILES ''' + filelist + '''
107 )
108 add_executable( ''' + testname + ''' ${SRC_FILES} )
109 source_group( "Common Files" FILES ${ELPP_FILES} ${CATCH_FILES} )
110 set_property(TARGET ''' + testname + ''' PROPERTY CXX_STANDARD 11)
111 target_link_libraries( ''' + testname + ''' ${DEPENDENCIES})
112 
113 set_target_properties( ''' + testname + ''' PROPERTIES FOLDER "Unit-Tests/''' + os.path.dirname( testdir ) + '''" )
114 
115 target_include_directories(''' + testname + ''' PRIVATE ''' + src + ''')
116 
117 ''' )
118  handle.close()
119 
120 # Recursively searches a .cpp file for #include directives and returns
121 # a set of all of them.
122 #
123 # Only directives that are relative to the current path (#include "<path>")
124 # are looked for!
125 #
126 def find_includes( filepath ):
127  filelist = set()
128  filedir = os.path.dirname(filepath)
129  for context in grep( '^\s*#\s*include\s+"(.*)"\s*$', filepath ):
130  m = context['match']
131  index = context['index']
132  include = m.group(1)
133  if not os.path.isabs( include ):
134  include = os.path.normpath( filedir + '/' + include )
135  include = include.replace( '\\', '/' )
136  if os.path.exists( include ):
137  filelist.add( include )
138  filelist |= find_includes( include )
139  return filelist
140 
141 def process_cpp( dir, builddir ):
142  found = []
143  shareds = []
144  statics = []
145  for f in find( dir, '(^|/)test-.*\.cpp$' ):
146  testdir = os.path.splitext( f )[0] # "log/internal/test-all" <- "log/internal/test-all.cpp"
147  testparent = os.path.dirname(testdir) # "log/internal"
148  # Each CMakeLists.txt sits in its own directory
149  os.makedirs( builddir + '/' + testdir, exist_ok = True ); # "build/log/internal/test-all"
150  # We need the project name unique: keep the path but make it nicer:
151  testname = 'test-' + testparent.replace( '/', '-' ) + '-' + os.path.basename(testdir)[5:] # "test-log-internal-all"
152  # Build the list of files we want in the project:
153  # At a minimum, we have the original file, plus any common files
154  filelist = [ dir + '/' + f, '${ELPP_FILES}', '${CATCH_FILES}' ]
155  # Add any "" includes specified in the .cpp that we can find
156  includes = find_includes( dir + '/' + f )
157  # Add any files explicitly listed in the .cpp itself, like this:
158  # //#cmake:add-file <filename>
159  # Any files listed are relative to $dir
160  shared = False
161  static = False
162  for context in grep( '^//#cmake:\s*', dir + '/' + f ):
163  m = context['match']
164  index = context['index']
165  cmd, *rest = context['line'][m.end():].split()
166  if cmd == 'add-file':
167  for additional_file in rest:
168  files = additional_file
169  if not os.path.isabs( additional_file ):
170  files = dir + '/' + testparent + '/' + additional_file
171  files = glob( files )
172  if not files:
173  error( f + '+' + str(index) + ': no files match "' + additional_file + '"' )
174  for abs_file in files:
175  abs_file = os.path.normpath( abs_file )
176  abs_file = abs_file.replace( '\\', '/' )
177  if not os.path.exists( abs_file ):
178  error( f + '+' + str(index) + ': file not found "' + additional_file + '"' )
179  debug( ' add file:', abs_file )
180  filelist.append( abs_file )
181  if( os.path.splitext( abs_file )[0] == 'cpp' ):
182  # Add any "" includes specified in the .cpp that we can find
183  includes |= find_includes( abs_file )
184  elif cmd == 'static!':
185  if len(rest):
186  error( f + '+' + str(index) + ': unexpected arguments past \'' + cmd + '\'' )
187  elif shared:
188  error( f + '+' + str(index) + ': \'' + cmd + '\' mutually exclusive with \'shared!\'' )
189  else:
190  static = True
191  elif cmd == 'shared!':
192  if len(rest):
193  error( f + '+' + str(index) + ': unexpected arguments past \'' + cmd + '\'' )
194  elif static:
195  error( f + '+' + str(index) + ': \'' + cmd + '\' mutually exclusive with \'static!\'' )
196  else:
197  shared = True
198  else:
199  error( f + '+' + str(index) + ': unknown cmd \'' + cmd + '\' (should be \'add-file\', \'static!\', or \'shared!\')' )
200  for include in includes:
201  filelist.append( include )
202  generate_cmake( builddir, testdir, testname, filelist )
203  if static:
204  statics.append( testdir )
205  elif shared:
206  shareds.append( testdir )
207  else:
208  found.append( testdir )
209  return found, shareds, statics
210 def process_py( dir, builddir ):
211  # TODO
212  return [],[],[]
213 
214 normal_tests = []
215 shared_tests = []
216 static_tests = []
217 n,sh,st = process_cpp( dir, builddir )
218 normal_tests.extend( n )
219 shared_tests.extend( sh )
220 static_tests.extend( st )
221 n,sh,st = process_py( dir, builddir )
222 normal_tests.extend( n )
223 shared_tests.extend( sh )
224 static_tests.extend( st )
225 
226 cmakefile = builddir + '/CMakeLists.txt'
227 name = os.path.basename( os.path.realpath( dir ))
228 debug( 'Creating "' + name + '" project in', cmakefile )
229 
230 handle = open( cmakefile, 'w' );
231 handle.write( '''
232 
233 # We make use of ELPP (EasyLogging++):
234 include_directories( ''' + dir + '''/../third-party/easyloggingpp/src )
235 set( ELPP_FILES
236  ''' + dir + '''/../third-party/easyloggingpp/src/easylogging++.cc
237  ''' + dir + '''/../third-party/easyloggingpp/src/easylogging++.h
238 )
239 set( CATCH_FILES
240  ''' + dir + '''/catch/catch.hpp
241 )
242 
243 ''' )
244 
245 n_tests = 0
246 for sdir in normal_tests:
247  handle.write( 'add_subdirectory( ' + sdir + ' )\n' )
248  debug( '... including:', sdir )
249  n_tests += 1
250 if len(shared_tests):
251  handle.write( 'if(NOT ${BUILD_SHARED_LIBS})\n' )
252  handle.write( ' message( INFO "' + str(len(shared_tests)) + ' shared lib unit-tests will be skipped. Check BUILD_SHARED_LIBS to run them..." )\n' )
253  handle.write( 'else()\n' )
254  for test in shared_tests:
255  handle.write( ' add_subdirectory( ' + test + ' )\n' )
256  debug( '... including:', sdir )
257  n_tests += 1
258  handle.write( 'endif()\n' )
259 if len(static_tests):
260  handle.write( 'if(${BUILD_SHARED_LIBS})\n' )
261  handle.write( ' message( INFO "' + str(len(static_tests)) + ' static lib unit-tests will be skipped. Uncheck BUILD_SHARED_LIBS to run them..." )\n' )
262  handle.write( 'else()\n' )
263  for test in static_tests:
264  handle.write( ' add_subdirectory( ' + test + ' )\n' )
265  debug( '... including:', sdir )
266  n_tests += 1
267  handle.write( 'endif()\n' )
268 handle.close()
269 
270 print( 'Generated ' + str(n_tests) + ' unit-tests' )
271 if have_errors:
272  exit(1)
273 exit(0)
274 
std::string join(const std::string &base, const std::string &path)
Definition: filesystem.h:113
def grep(expr, args)
std::vector< uint32_t > split(const std::string &s, char delim)
static void glob(const std::string &directory, const std::string &spec, std::function< void(std::string const &) > fn, bool recursive=true, bool includeDirectories=false)
Definition: filesystem.h:269
def find_includes(filepath)
static std::string print(const transformation &tf)
static const textual_icon exit
Definition: model-views.h:254
def grep_(pattern, lines, context)
def process_py(dir, builddir)
def generate_cmake(builddir, testdir, testname, filelist)
def process_cpp(dir, builddir)
def remove_newlines(lines)
def find(dir, mask)


librealsense2
Author(s): Sergey Dorodnicov , Doron Hirshberg , Mark Horn , Reagan Lopez , Itay Carpis
autogenerated on Mon May 3 2021 02:50:13