lib/jmeter_perf.rb in jmeter_perf-0.0.6 vs lib/jmeter_perf.rb in jmeter_perf-0.0.7

- old
+ new

@@ -21,24 +21,159 @@ Dir.glob(File.join(lib, "jmeter_perf/plugins/*.rb")).each do |file| require_relative file end -require_relative "jmeter_perf/dsl" - +# JmeterPerf module for handling performance testing with JMeter. +# This module provides methods to define and execute JMeter test plans. module JmeterPerf - def self.test(params = {}, &) - JmeterPerf.dsl_eval(JmeterPerf::ExtendedDSL.new(params), &) - end + # Evaluates the test plan with the given parameters and block. + # @see https://github.com/jlurena/jmeter_perf/wiki/1.-DSL-Documentation DSL Documentation + # @param [Hash] params Parameters for the test plan (default: `{}`). + # @yield The block to define the test plan steps. + # @return [JmeterPerf::ExtendedDSL] The DSL instance with the configured test plan. + def self.test(params = {}, &block) + dsl = JmeterPerf::ExtendedDSL.new(params) - def self.dsl_eval(dsl, &block) block_context = eval("self", block.binding, __FILE__, __LINE__) proxy_context = JmeterPerf::FallbackContextProxy.new(dsl, block_context) begin - block_context.instance_variables.each { |ivar| proxy_context.instance_variable_set(ivar, block_context.instance_variable_get(ivar)) } + block_context.instance_variables.each do |ivar| + proxy_context.instance_variable_set(ivar, block_context.instance_variable_get(ivar)) + end proxy_context.instance_eval(&block) ensure - block_context.instance_variables.each { |ivar| block_context.instance_variable_set(ivar, proxy_context.instance_variable_get(ivar)) } + block_context.instance_variables.each do |ivar| + block_context.instance_variable_set(ivar, proxy_context.instance_variable_get(ivar)) + end end dsl + end + + # DSL class for defining JMeter test plans. + # Provides methods to generate, save, and run JMeter tests. + class ExtendedDSL < DSL + include Parser + attr_accessor :root + + # Initializes an ExtendedDSL object with the provided parameters. + # @param [Hash] params Parameters for the test plan (default: `{}`). + def initialize(params = {}) + @root = Nokogiri::XML(<<-EOF.strip_heredoc) + <?xml version="1.0" encoding="UTF-8"?> + <jmeterTestPlan version="1.2" properties="3.1" jmeter="3.1" ruby-jmeter="3.0"> + <hashTree> + </hashTree> + </jmeterTestPlan> + EOF + node = JmeterPerf::TestPlan.new(params) + @current_node = @root.at_xpath("//jmeterTestPlan/hashTree") + @current_node = attach_to_last(node) + end + + # Saves the test plan as a JMX file. + # + # @param [String] out_jmx The path for the output JMX file (default: `"ruby-jmeter.jmx"`). + def jmx(out_jmx: "ruby-jmeter.jmx") + File.write(out_jmx, doc.to_xml(indent: 2)) + logger.info "JMX saved to: #{out_jmx}" + end + + # Runs the test plan with the specified configuration. + # + # @param [String] name The name of the test run (default: `"ruby-jmeter"`). + # @param [String] jmeter_path Path to the JMeter executable (default: `"jmeter"`). + # @param [String] out_jmx The filename for the output JMX file (default: `"ruby-jmeter.jmx"`). + # @param [String] out_jtl The filename for the output JTL file (default: `"jmeter.jtl"`). + # @param [String] out_jmeter_log The filename for the JMeter log file (default: `"jmeter.log"`). + # @param [String] out_cmd_log The filename for the command log file (default: `"jmeter-cmd.log"`). + # @return [JmeterPerf::Report::Summary] The summary report of the test run. + # @raise [RuntimeError] If the test execution fails. + def run( + name: "ruby-jmeter", + jmeter_path: "jmeter", + out_jmx: "ruby-jmeter.jmx", + out_jtl: "jmeter.jtl", + out_jmeter_log: "jmeter.log", + out_cmd_log: "jmeter-cmd.log" + ) + jmx(out_jmx:) + logger.warn "Executing #{out_jmx} test plan locally ..." + + cmd = <<~CMD.strip + #{jmeter_path} -n -t #{out_jmx} -j #{out_jmeter_log} -l #{out_jtl} + CMD + + summary = JmeterPerf::Report::Summary.new(out_jtl, name) + jtl_process_thread = summary.stream_jtl_async + + File.open(out_cmd_log, "w") do |f| + pid = Process.spawn(cmd, out: f, err: [:child, :out]) + Process.waitpid(pid) + end + + summary.finish! # Notify jtl collection that JTL cmd finished + jtl_process_thread.join # Join main thread and wait for it to finish + + unless $?.exitstatus.zero? + logger.error("Failed to run #{cmd}. See #{out_cmd_log} and #{out_jmeter_log} for details.") + raise "Exit status: #{$?.exitstatus}" + end + + summary.summarize_data! + logger.info "[Test Plan Execution Completed Successfully] JTL saved to: #{out_jtl}\n" + summary + rescue + summary.finish! + raise + end + + private + + # Creates a new hash tree node for the JMeter test plan. + # + # @return [Nokogiri::XML::Node] A new hash tree node. + def hash_tree + Nokogiri::XML::Node.new("hashTree", @root) + end + + # Attaches a node as the last child of the current node. + # + # @param [Object] node The node to attach. + # @return [Object] The hash tree for the attached node. + def attach_to_last(node) + ht = hash_tree + last_node = @current_node + last_node << node.doc.children << ht + ht + end + + # Attaches a node and evaluates the block within its context. + # + # @param [Object] node The node to attach. + # @yield The block to be executed in the node's context. + def attach_node(node, &block) + ht = attach_to_last(node) + previous = @current_node + @current_node = ht + instance_exec(&block) if block + @current_node = previous + end + + # Returns the Nokogiri XML document. + # + # @return [Nokogiri::XML::Document] The XML document for the JMeter test plan. + def doc + Nokogiri::XML(@root.to_s, &:noblanks) + end + + # Logger instance to log messages to the standard output. + # + # @return [Logger] The logger instance, initialized to DEBUG level. + def logger + return @logger if @logger + @logger = Logger.new($stdout) + @logger.level = Logger::DEBUG + @logger + end end end