Class: OpenStudioMeasureTester::OpenStudioStyle
- Inherits:
-
Object
- Object
- OpenStudioMeasureTester::OpenStudioStyle
- Defined in:
- lib/openstudio_measure_tester/openstudio_style.rb
Constant Summary collapse
- CHECKS =
[ { regex: /OpenStudio::Ruleset::ModelUserScript/, check_type: :if_exists, message: 'OpenStudio::Ruleset::ModelUserScript is deprecated, use OpenStudio::Measure::ModelMeasure instead.', type: :deprecated, severity: :error, file_type: :measure }, { regex: /OpenStudio::Ruleset::WorkspaceUserScript/, check_type: :if_exists, message: 'OpenStudio::Ruleset::WorkspaceUserScript is deprecated, use OpenStudio::Measure::EnergyPlusMeasure instead.', type: :deprecated, severity: :error, file_type: :measure }, { regex: /OpenStudio::Ruleset::ReportingUserScript/, check_type: :if_exists, message: 'OpenStudio::Ruleset::ReportingUserScript is deprecated, use OpenStudio::Measure::ReportingMeasure instead.', type: :deprecated, severity: :error, file_type: :measure }, { regex: /OpenStudio::Ruleset::OSRunner/, check_type: :if_exists, message: 'OpenStudio::Ruleset::OSRunner is deprecated, use OpenStudio::Measure::OSRunner.new(OpenStudio::WorkflowJSON.new) instead.', type: :deprecated, severity: :error, file_type: :measure }, { regex: /OpenStudio::Ruleset::OSRunner/, check_type: :if_exists, message: 'OpenStudio::Ruleset::OSRunner is deprecated, use OpenStudio::Measure::OSRunner.new(OpenStudio::WorkflowJSON.new) instead.', type: :deprecated, severity: :error, file_type: :test }, { regex: /OpenStudio::Ruleset::OSArgumentVector/, check_type: :if_exists, message: 'OpenStudio::Ruleset::OSArgumentVector is deprecated, use OpenStudio::Measure::OSArgumentVector instead.', type: :deprecated, severity: :error, file_type: :measure }, { regex: /OpenStudio::Ruleset::OSArgument/, check_type: :if_exists, message: 'OpenStudio::Ruleset::OSArgument is deprecated, use OpenStudio::Measure::OSArgument instead.', type: :deprecated, severity: :error, file_type: :measure }, { regex: /OpenStudio::Ruleset::OSArgumentMap/, check_type: :if_exists, message: 'OpenStudio::Ruleset::OSArgumentMap is deprecated, use OpenStudio::Measure.convertOSArgumentVectorToMap(arguments) instead.', type: :deprecated, severity: :error, file_type: :measure }, { regex: /MiniTest::Unit::TestCase/, check_type: :if_exists, message: 'MiniTest::Unit::TestCase is deprecated. Use MiniTest::Test.', type: :syntax, severity: :warning, file_type: :test }, { regex: %r{require .openstudio/ruleset }, check_type: :if_exists, message: "Require openstudio/ruleset/* is deprecated. Use require 'openstudio/measure/*'", type: :syntax, severity: :warning, file_type: :test } ].freeze
Instance Attribute Summary collapse
-
#measure_messages ⇒ Object
readonly
Returns the value of attribute measure_messages.
-
#results ⇒ Object
readonly
Returns the value of attribute results.
Instance Method Summary collapse
-
#aggregate_results ⇒ Object
read the data in the results and sum up the total number of issues for all measures.
- #check(data, check) ⇒ Object
-
#generate_measure_hash(measure_dir, measure, measure_info) ⇒ Object
These methods are copied from the measure_manager.rb file in OpenStudio.
- #get_arguments_from_measure_info(measure_info) ⇒ Object
- #get_attributes_from_measure(measure_dir, class_name) ⇒ Object
-
#initialize(results_dir, measures_glob) ⇒ OpenStudioStyle
constructor
Pass in the measures_glob with the filename (typically measure.rb).
- #log_message(message, type = :syntax, severity = :info) ⇒ Object
- #run_regex_checks(measure_dir) ⇒ Object
- #save_results ⇒ Object
- #test_measure(measure_dir) ⇒ Object
-
#validate_measure_hash(measure_hash) ⇒ Object
Validate the measure hash to make sure that it is meets the style guide.
-
#validate_name(name_type, name, severity = :info, options = {}) ⇒ Object
check the name of the measure and make sure that there are no unwanted characters.
Constructor Details
#initialize(results_dir, measures_glob) ⇒ OpenStudioStyle
Pass in the measures_glob with the filename (typically measure.rb)
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 87 def initialize(results_dir, measures_glob) @measures_glob = measures_glob @results = { by_measure: {} } @results_dir = results_dir # Individual measure messages = [] # Load in the method infoExtractor which will load measure info (helpful comment huh?) # https://github.com/NREL/OpenStudio/blob/e7aa6be05a714814983d68ea840ca61383e9ef54/openstudiocore/src/measure/OSMeasureInfoGetter.cpp#L254 eval(::OpenStudio::Measure.infoExtractorRubyFunction) Dir[@measures_glob].each do |measure| measure_dir = File.dirname(measure) # initialize the measure name from the directory until the measure_hash is loaded. # The test_measure method can fail but still report errors, so we need a place to store the results. @results[:by_measure].merge!(test_measure(measure_dir)) end aggregate_results end |
Instance Attribute Details
#measure_messages ⇒ Object (readonly)
Returns the value of attribute measure_messages.
10 11 12 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 10 def end |
#results ⇒ Object (readonly)
Returns the value of attribute results.
10 11 12 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 10 def results @results end |
Instance Method Details
#aggregate_results ⇒ Object
read the data in the results and sum up the total number of issues for all measures
190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 190 def aggregate_results total_info = 0 total_warnings = 0 total_errors = 0 @results[:by_measure].each_pair do |k, v| total_info += v[:measure_info] total_warnings += v[:measure_warnings] total_errors += v[:measure_errors] end @results[:total_info] = total_info @results[:total_warnings] = total_warnings @results[:total_errors] = total_errors end |
#check(data, check) ⇒ Object
215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 215 def check(data, check) if check[:check_type] == :if_exists if data.match?(check[:regex]) (check[:message], check[:type], check[:severity]) end elsif check[:check_type] == :if_missing if !data&.match?(check[:regex]) (check[:message], check[:type], check[:severity]) end end end |
#generate_measure_hash(measure_dir, measure, measure_info) ⇒ Object
These methods are copied from the measure_manager.rb file in OpenStudio. Once the measure_manager.rb file is shipped with OpenStudio, we can deprecate the copying over.
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 373 def generate_measure_hash(measure_dir, measure, measure_info) result = {} result[:measure_dir] = measure_dir result[:name] = measure.name result[:directory] = measure.directory.to_s if measure.error.is_initialized result[:error] = measure.error.get end result[:uid] = measure.uid result[:uuid] = measure.uuid.to_s result[:version_id] = measure.versionId result[:version_uuid] = measure.versionUUID.to_s version_modified = measure.versionModified if version_modified.is_initialized result[:version_modified] = version_modified.get.toISO8601 else result[:version_modified] = nil end result[:xml_checksum] = measure.xmlChecksum result[:display_name] = measure.displayName result[:class_name] = measure.className result[:description] = measure.description result[:modeler_description] = measure.modelerDescription result[:tags] = [] measure..each { |tag| result[:tags] << tag } result[:outputs] = [] begin # this is an OS 2.0 only method measure.outputs.each do |output| out = {} out[:name] = output.name out[:display_name] = output.displayName out[:short_name] = output.shortName.get if output.shortName.is_initialized out[:description] = output.description out[:type] = output.type out[:units] = output.units.get if output.units.is_initialized out[:model_dependent] = output.modelDependent result[:outputs] << out end rescue StandardError end attributes = [] measure.attributes.each do |a| value_type = a.valueType if value_type == 'Boolean'.to_AttributeValueType attributes << { name: a.name, display_name: a.displayName(true).get, value: a.valueAsBoolean } elsif value_type == 'Double'.to_AttributeValueType attributes << { name: a.name, display_name: a.displayName(true).get, value: a.valueAsDouble } elsif value_type == 'Integer'.to_AttributeValueType attributes << { name: a.name, display_name: a.displayName(true).get, value: a.valueAsInteger } elsif value_type == 'Unsigned'.to_AttributeValueType attributes << { name: a.name, display_name: a.displayName(true).get, value: a.valueAsUnsigned } elsif value_type == 'String'.to_AttributeValueType attributes << { name: a.name, display_name: a.displayName(true).get, value: a.valueAsString } end end result[:attributes] = attributes result[:arguments] = measure_info ? get_arguments_from_measure_info(measure_info) : [] result end |
#get_arguments_from_measure_info(measure_info) ⇒ Object
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 438 def get_arguments_from_measure_info(measure_info) result = [] measure_info.arguments.each do |argument| type = argument.type arg = {} arg[:name] = argument.name arg[:display_name] = argument.displayName arg[:description] = argument.description.to_s arg[:type] = argument.type.valueName arg[:required] = argument.required arg[:model_dependent] = argument.modelDependent if type == 'Boolean'.to_OSArgumentType arg[:default_value] = argument.defaultValueAsBool if argument.hasDefaultValue elsif type == 'Double'.to_OSArgumentType arg[:units] = argument.units.get if argument.units.is_initialized arg[:default_value] = argument.defaultValueAsDouble if argument.hasDefaultValue elsif type == 'Quantity'.to_OSArgumentType arg[:units] = argument.units.get if argument.units.is_initialized arg[:default_value] = argument.defaultValueAsQuantity.value if argument.hasDefaultValue elsif type == 'Integer'.to_OSArgumentType arg[:units] = argument.units.get if argument.units.is_initialized arg[:default_value] = argument.defaultValueAsInteger if argument.hasDefaultValue elsif type == 'String'.to_OSArgumentType arg[:default_value] = argument.defaultValueAsString if argument.hasDefaultValue elsif type == 'Choice'.to_OSArgumentType arg[:default_value] = argument.defaultValueAsString if argument.hasDefaultValue arg[:choice_values] = [] argument.choiceValues.each { |value| arg[:choice_values] << value } arg[:choice_display_names] = [] argument.choiceValueDisplayNames.each { |value| arg[:choice_display_names] << value } elsif type == 'Path'.to_OSArgumentType arg[:default_value] = argument.defaultValueAsPath.to_s if argument.hasDefaultValue end result << arg end result end |
#get_attributes_from_measure(measure_dir, class_name) ⇒ Object
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 340 def get_attributes_from_measure(measure_dir, class_name) result = { values_from_file: { name: '', display_name: '', description: '', modeler_description: '' } } begin # file exists because the init checks for its existence require "#{measure_dir}/measure.rb" measure = Object.const_get(class_name).new rescue LoadError => e ("Could not load measure into memory with error #{e.message}", :initialize, :error) return result end result[:values_from_file][:name] = class_name.to_snakecase result[:values_from_file][:display_name] = measure.name result[:values_from_file][:description] = measure.description result[:values_from_file][:modeler_description] = measure.modeler_description result end |
#log_message(message, type = :syntax, severity = :info) ⇒ Object
180 181 182 183 184 185 186 187 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 180 def (, type = :syntax, severity = :info) = { message:, type:, severity: } << end |
#run_regex_checks(measure_dir) ⇒ Object
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 214 def run_regex_checks(measure_dir) def check(data, check) if check[:check_type] == :if_exists if data.match?(check[:regex]) (check[:message], check[:type], check[:severity]) end elsif check[:check_type] == :if_missing if !data&.match?(check[:regex]) (check[:message], check[:type], check[:severity]) end end end file_data = File.read("#{measure_dir}/measure.rb") test_files = Dir["#{measure_dir}/**/*_[tT]est.rb"] CHECKS.each do |check| if check[:file_type] == :test test_files.each do |test_file| puts test_file test_filedata = File.read(test_file) check(test_filedata, check) end elsif check[:file_type] == :measure check(file_data, check) end end end |
#save_results ⇒ Object
204 205 206 207 208 209 210 211 212 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 204 def save_results save_dir = "#{@results_dir}/openstudio_style" save_file = "#{save_dir}/openstudio_style.json" puts "Saving OpenStudio Style results to #{save_file}" FileUtils.mkdir_p save_dir unless Dir.exist? save_dir File.open(save_file, 'w') do |file| file << JSON.pretty_generate(@results) end end |
#test_measure(measure_dir) ⇒ Object
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 110 def test_measure(measure_dir) # reset the instance variables for this measure .clear # initialize the measure name from the directory until the measure_hash is loaded. # The test_measure method can fail but still report errors, so we need a place to store the results. @measure_classname = measure_dir.split('/').last measure_missing = false unless Dir.exist? measure_dir ("Could not find measure directory: '#{measure_dir}'.", :general, :error) measure_missing = true end unless File.exist? "#{measure_dir}/measure.rb" ("Could not find measure.rb in '#{measure_dir}'.", :general, :error) measure_missing = true end unless File.exist? "#{measure_dir}/measure.xml" ("Could not find measure.xml in '#{measure_dir}'.", :general, :error) measure_missing = true end # Test the existence of LICENSE.md unless File.exist? "#{measure_dir}/LICENSE.md" ("Could not find LICENSE.md in '#{measure_dir}'.", :general, :warning) end unless measure_missing measure = OpenStudio::BCLMeasure.load(measure_dir) if measure.empty? ("Failed to load measure '#{measure_dir}'", :general, :error) else measure = measure.get begin # there seems to be a race condition with the infoExtractor method measure_info = infoExtractor(measure, OpenStudio::Model::OptionalModel.new, OpenStudio::OptionalWorkspace.new) rescue NameError => e ("Unable to parse info from measure. Error: '#{e}'", :general, :error) rescue StandardError => e ("Unknown error extracting measure info. Error #{e}", :general, :error) end measure_hash = generate_measure_hash(measure_dir, measure, measure_info) measure_hash.merge!(get_attributes_from_measure(measure_dir, measure_hash[:class_name])) @measure_classname = measure_hash[:class_name] # At this point, the measure.rb file is ensured to exist # run static checks on the files in the measure directory run_regex_checks(measure_dir) validate_measure_hash(measure_hash) end end # pp @measure_messages # calculate the info, warnings, errors and return the measure data # TODO: break out the issues by file return { @measure_classname.to_sym => { measure_info: .nil? ? 0 : .count { |h| h[:severity] == :info }, measure_warnings: .nil? ? 0 : .count { |h| h[:severity] == :warning }, measure_errors: .nil? ? 0 : .count { |h| h[:severity] == :error }, issues: .clone } } end |
#validate_measure_hash(measure_hash) ⇒ Object
Validate the measure hash to make sure that it is meets the style guide. This will also perform the selection of which data to use for the “actual metadata”
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 287 def validate_measure_hash(measure_hash) validate_name('Measure name', measure_hash[:name], :error, ensure_snakecase: true) validate_name('Class name', measure_hash[:class_name], :error, ensure_camelcase: true) # check if @measure_name (which is the directory name) is snake cased validate_name('Measure directory name', measure_hash[:measure_dir].split('/').last, :warning, ensure_snakecase: true) ('Could not find measure description in XML.', :structure, :warning) if measure_hash[:description].empty? ('Could not find modeler description in XML.', :structure, :warning) unless measure_hash[:modeler_description] ('Could not find display_name in measure.', :structure, :warning) unless measure_hash[:display_name] ('Could not find measure name in measure.', :structure, :warning) unless measure_hash[:name] # def name is the display name in the XML! if measure_hash[:values_from_file][:display_name].empty? ('Could not find "def name" in measure.rb', :structure, :error) elsif measure_hash[:display_name] != measure_hash[:values_from_file][:display_name] ("'def name' in measure.rb differs from <display_name> in XML. Run openstudio measure -u .", :structure, :error) end if measure_hash[:values_from_file][:name] != measure_hash[:name] ('Measure class as snake_case name does not match <name> in XML. Run openstudio measure -u .', :structure, :error) end if measure_hash[:values_from_file][:description].empty? ('Could not find "def description" in measure.rb', :structure, :error) elsif measure_hash[:description] != measure_hash[:values_from_file][:description] ('Description in measure.rb differs from description in XML', :structure, :error) end if measure_hash[:values_from_file][:description].empty? ('Could not find "def modeler_description" in measure.rb', :structure, :error) if measure_hash[:values_from_file][:description].empty? elsif measure_hash[:description] != measure_hash[:values_from_file][:description] ('Modeler description in measure.rb differs from modeler description in XML', :structure, :error) end measure_hash[:arguments].each do |arg| validate_name('Argument display name', arg[:display_name], :error) # { # :name => "relative_building_rotation", # :display_name => # "Number of Degrees to Rotate Building (positive value is clockwise).", # :description => "", # :type => "Double", # :required => true, # :model_dependent => false, # :default_value => 90.0 # } end end |
#validate_name(name_type, name, severity = :info, options = {}) ⇒ Object
check the name of the measure and make sure that there are no unwanted characters
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/openstudio_measure_tester/openstudio_style.rb', line 250 def validate_name(name_type, name, severity = :info, = {}) clean_name = name # Check for parenthetical names if clean_name.match?(/\(.+?\)/) ("#{name_type} '#{name}' appears to have units. Set units in the setUnits method.", severity) end if clean_name.match?(/\?|\.|\#/) ("#{name_type} '#{name}' cannot contain ?#.[] characters.", :syntax, severity) end if [:ensure_camelcase] # convert to snake and then back to camelcase to check if formatted correctly if clean_name != clean_name.strip ("#{name_type} '#{name}' has leading or trailing spaces.", :syntax, severity) end if clean_name != clean_name.to_snakecase.to_camelcase ("#{name_type} '#{name}' is not CamelCase.", :syntax, severity) end end if [:ensure_snakecase] # no leading/trailing spaces if clean_name != clean_name.strip ("#{name_type} '#{name}' has leading or trailing spaces.", :syntax, severity) end if clean_name != clean_name.to_snakecase ("#{name_type} '#{name}' is not snake_case.", :syntax, severity) end end end |