|
| 1 | +require 'fileutils' |
| 2 | +require 'open3' |
| 3 | +require 'tmpdir' |
| 4 | +require 'test/unit' |
| 5 | + |
| 6 | +module TestLib |
| 7 | + TMP_DIR = File.expand_path(ENV['CPM_INTEGRATION_TEST_DIR'] || File.join(Dir.tmpdir, 'cpm-test', Time.now.strftime('%Y_%m_%d-%H_%M_%S'))) |
| 8 | + CPM_PATH = File.expand_path('../../cmake/CPM.cmake', __dir__) |
| 9 | + |
| 10 | + TEMPLATES_DIR = File.expand_path('templates', __dir__) |
| 11 | + |
| 12 | + # Environment variables which are read by cpm |
| 13 | + CPM_ENV = %w( |
| 14 | + CPM_USE_LOCAL_PACKAGES |
| 15 | + CPM_LOCAL_PACKAGES_ONLY |
| 16 | + CPM_DOWNLOAD_ALL |
| 17 | + CPM_DONT_UPDATE_MODULE_PATH |
| 18 | + CPM_DONT_CREATE_PACKAGE_LOCK |
| 19 | + CPM_INCLUDE_ALL_IN_PACKAGE_LOCK |
| 20 | + CPM_USE_NAMED_CACHE_DIRECTORIES |
| 21 | + CPM_SOURCE_CACHE |
| 22 | + ) |
| 23 | + def self.clear_env |
| 24 | + CPM_ENV.each { ENV[_1] = nil } |
| 25 | + end |
| 26 | +end |
| 27 | + |
| 28 | +puts "Warning: test directory '#{TestLib::TMP_DIR}' already exists" if File.exist?(TestLib::TMP_DIR) |
| 29 | +raise "Cannot find 'CPM.cmake' at '#{TestLib::CPM_PATH}'" if !File.file?(TestLib::CPM_PATH) |
| 30 | + |
| 31 | +puts "Running CPM.cmake integration tests" |
| 32 | +puts "Temp directory: '#{TestLib::TMP_DIR}'" |
| 33 | + |
| 34 | +# Clean all CPM-related env vars |
| 35 | +TestLib.clear_env |
| 36 | + |
| 37 | +class Project |
| 38 | + def initialize(src_dir, bin_dir) |
| 39 | + @src_dir = src_dir |
| 40 | + @bin_dir = bin_dir |
| 41 | + end |
| 42 | + |
| 43 | + attr :src_dir, :bin_dir |
| 44 | + |
| 45 | + def create_file(target_path, text, args = {}) |
| 46 | + target_path = File.join(@src_dir, target_path) |
| 47 | + |
| 48 | + # tweak args |
| 49 | + args[:cpm_path] = TestLib::CPM_PATH if !args[:cpm_path] |
| 50 | + args[:packages] = [args[:package]] if args[:package] # if args contain package, create the array |
| 51 | + args[:packages] = args[:packages].join("\n") if args[:packages] # join all packages if any |
| 52 | + |
| 53 | + File.write target_path, text % args |
| 54 | + end |
| 55 | + |
| 56 | + def create_file_from_template(target_path, source_path, args = {}) |
| 57 | + source_path = File.join(@src_dir, source_path) |
| 58 | + raise "#{source_path} doesn't exist" if !File.file?(source_path) |
| 59 | + src_text = File.read source_path |
| 60 | + create_file target_path, src_text, args |
| 61 | + end |
| 62 | + |
| 63 | + # common function to create ./CMakeLists.txt from ./lists.in.cmake |
| 64 | + def create_lists_from_default_template(args = {}) |
| 65 | + create_file_from_template 'CMakeLists.txt', 'lists.in.cmake', args |
| 66 | + end |
| 67 | + |
| 68 | + CommandResult = Struct.new :out, :err, :status |
| 69 | + def configure(extra_args = '') |
| 70 | + CommandResult.new *Open3.capture3("cmake -S #{@src_dir} -B #{@bin_dir} #{extra_args}") |
| 71 | + end |
| 72 | + def build(extra_args = '') |
| 73 | + CommandResult.new *Open3.capture3("cmake --build #{@bin_dir} #{extra_args}") |
| 74 | + end |
| 75 | + |
| 76 | + class CMakeCache |
| 77 | + class Entry |
| 78 | + def initialize(val, type, advanced, desc) |
| 79 | + @val = val |
| 80 | + @type = type |
| 81 | + @advanced = advanced |
| 82 | + @desc = desc |
| 83 | + end |
| 84 | + attr :val, :type, :advanced, :desc |
| 85 | + alias_method :advanced?, :advanced |
| 86 | + def inspect |
| 87 | + "(#{val.inspect} #{type}" + (advanced? ? ' ADVANCED)' : ')') |
| 88 | + end |
| 89 | + end |
| 90 | + |
| 91 | + Package = Struct.new(:ver, :src_dir, :bin_dir) |
| 92 | + |
| 93 | + def self.from_dir(dir) |
| 94 | + entries = {} |
| 95 | + cur_desc = '' |
| 96 | + file = File.join(dir, 'CMakeCache.txt') |
| 97 | + return nil if !File.file?(file) |
| 98 | + File.readlines(file).each { |line| |
| 99 | + line.strip! |
| 100 | + next if line.empty? |
| 101 | + next if line.start_with? '#' # comment |
| 102 | + if line.start_with? '//' |
| 103 | + cur_desc += line[2..] |
| 104 | + else |
| 105 | + m = /(.+?)(-ADVANCED)?:([A-Z]+)=(.*)/.match(line) |
| 106 | + raise "Error parsing '#{line}' in #{file}" if !m |
| 107 | + entries[m[1]] = Entry.new(m[4], m[3], !!m[2], cur_desc) |
| 108 | + cur_desc = '' |
| 109 | + end |
| 110 | + } |
| 111 | + CMakeCache.new entries |
| 112 | + end |
| 113 | + |
| 114 | + def initialize(entries) |
| 115 | + @entries = entries |
| 116 | + |
| 117 | + package_list = self['CPM_PACKAGES'] |
| 118 | + @packages = if package_list |
| 119 | + # collect package data |
| 120 | + @packages = package_list.split(';').map { |name| |
| 121 | + [name, Package.new( |
| 122 | + self["CPM_PACKAGE_#{name}_VERSION"], |
| 123 | + self["CPM_PACKAGE_#{name}_SOURCE_DIR"], |
| 124 | + self["CPM_PACKAGE_#{name}_BINARY_DIR"] |
| 125 | + )] |
| 126 | + }.to_h |
| 127 | + else |
| 128 | + {} |
| 129 | + end |
| 130 | + end |
| 131 | + |
| 132 | + attr :entries, :packages |
| 133 | + |
| 134 | + def [](key) |
| 135 | + e = @entries[key] |
| 136 | + return nil if !e |
| 137 | + e.val |
| 138 | + end |
| 139 | + end |
| 140 | + def read_cache |
| 141 | + CMakeCache.from_dir @bin_dir |
| 142 | + end |
| 143 | +end |
| 144 | + |
| 145 | +class IntegrationTest < Test::Unit::TestCase |
| 146 | + self.test_order = :defined # run tests in order of defintion (as opposed to alphabetical) |
| 147 | + |
| 148 | + def cleanup |
| 149 | + # Clear cpm-related env vars which may have been set by the test |
| 150 | + TestLib.clear_env |
| 151 | + end |
| 152 | + |
| 153 | + # extra assertions |
| 154 | + |
| 155 | + def assert_success(res) |
| 156 | + msg = build_message(nil, "command status was expected to be a success, but failed with code <?> and STDERR:\n\n#{res.err}", res.status.to_i) |
| 157 | + assert_block(msg) { res.status.success? } |
| 158 | + end |
| 159 | + |
| 160 | + def assert_same_path(a, b) |
| 161 | + msg = build_message(nil, "<?> expected but was\n<?>", a, b) |
| 162 | + assert_block(msg) { File.identical? a, b } |
| 163 | + end |
| 164 | + |
| 165 | + # utils |
| 166 | + class << self |
| 167 | + def startup |
| 168 | + @@test_dir = File.join(TestLib::TMP_DIR, self.name. |
| 169 | + # to-underscore conversion from Rails |
| 170 | + gsub(/::/, '/'). |
| 171 | + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). |
| 172 | + gsub(/([a-z\d])([A-Z])/,'\1_\2'). |
| 173 | + tr("-", "_"). |
| 174 | + downcase |
| 175 | + ) |
| 176 | + end |
| 177 | + end |
| 178 | + |
| 179 | + def cur_test_dir |
| 180 | + @@test_dir |
| 181 | + end |
| 182 | + |
| 183 | + def make_project(template_dir = nil) |
| 184 | + test_name = local_name |
| 185 | + test_name = test_name[5..] if test_name.start_with?('test_') |
| 186 | + |
| 187 | + base = File.join(cur_test_dir, test_name) |
| 188 | + src_dir = base + '-src' |
| 189 | + |
| 190 | + FileUtils.mkdir_p src_dir |
| 191 | + |
| 192 | + if template_dir |
| 193 | + template_dir = File.join(TestLib::TEMPLATES_DIR, template_dir) |
| 194 | + raise "#{template_dir} is not a directory" if !File.directory?(template_dir) |
| 195 | + FileUtils.copy_entry template_dir, src_dir |
| 196 | + end |
| 197 | + |
| 198 | + Project.new src_dir, base + '-bin' |
| 199 | + end |
| 200 | +end |
0 commit comments