Module: Metasploit::Framework::Spec::Constants::Suite

Defined in:
lib/metasploit/framework/spec/constants/suite.rb

Overview

Logs if constants created by module loading are left over after suite has completed.

Constant Summary collapse

LOGS_PATHNAME =

CONSTANTS

Pathname.new('log/metasploit/framework/spec/constants/suite')

Class Method Summary collapse

Class Method Details

.configure!Object

Configures after(:suite) callback for RSpec to check for leaked constants.


35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/metasploit/framework/spec/constants/suite.rb', line 35

def self.configure!
  unless @configured
    RSpec.configure do |config|
      config.before(:suite) do
        Metasploit::Framework::Spec::Constants::Suite.log_leaked_constants(
            :before,
            'Modules are being loaded outside callbacks before suite starts.'
        )
      end

      config.after(:suite) do
        Metasploit::Framework::Spec::Constants::Suite.log_leaked_constants(
            :after,
            'Modules are being loaded inside callbacks or examples during suite run.'
        )
      end
    end

    @configured = true
  end
end

.define_taskvoid

This method returns an undefined value.

Adds action to `spec` task so that `rake spec` fails if `log/leaked-constants.log` exists after printing out the leaked constants.


61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/metasploit/framework/spec/constants/suite.rb', line 61

def self.define_task
  Rake::Task.define_task(:spec) do
    leaked_before = Metasploit::Framework::Spec::Constants::Suite.print_leaked_constants(:before)
    leaked_after = Metasploit::Framework::Spec::Constants::Suite.print_leaked_constants(:after)

    # leaks after suite can be be cleaned up by {Metasploit::Framework::Spec::Constants::Each.configure!}, but
    # leaks before suite require user intervention to find the leaks since it's a programming error in how the specs
    # are written where Modules are being loaded in the context scope.
    if leaked_after
      $stderr.puts
      $stderr.puts "Add `Metasploit::Framework::Spec::Constants::Each.configure!` to `spec/spec_helper.rb` " \
                   "**NOTE: `Metasploit::Framework::Spec::Constants::Each` may report false leaks if `after(:all)` " \
                   "is used to clean up constants instead of `after(:each)`**"
    end

    if leaked_before || leaked_after
      exit 1
    end
  end
end

.log_leaked_constants(hook, message) ⇒ void

This method returns an undefined value.

Logs leaked constants to LOG_PATHNAME and prints `message` to stderr.

Parameters:

  • message (String)

    additional message printed to stderr when there is at least one leaked constant.

  • hook (:after, :before)

    Whether the log is recording leaked constants `:before` the suite runs or `:after` the suite runs.


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/metasploit/framework/spec/constants/suite.rb', line 14

def self.log_leaked_constants(hook, message)
  count = 0
  hook_log_pathname = log_pathname(hook)
  hook_log_pathname.parent.mkpath

  hook_log_pathname.open('w') do |f|
    count = Metasploit::Framework::Spec::Constants.each do |child_name|
      f.puts child_name
    end
  end

  if count > 0
    $stderr.puts "#{count} #{'constant'.pluralize(count)} leaked under " \
                 "#{Metasploit::Framework::Spec::Constants::PARENT_CONSTANT}. #{message} See #{hook_log_pathname} " \
                 "for details."
  else
    hook_log_pathname.delete
  end
end

.log_pathname(hook) ⇒ Object

Parameters:

  • hook (:after, :before)

    Whether the log is recording leaked constants `:before` the suite runs or `:after` the suite runs.


84
85
86
# File 'lib/metasploit/framework/spec/constants/suite.rb', line 84

def self.log_pathname(hook)
  LOGS_PATHNAME.join("#{hook}.log")
end

Prints logged leaked constants to stderr.

Parameters:

  • hook (:after, :before)

    Whether the log is recording leaked constants `:before` the suite runs or `:after` the suite runs.

Returns:

  • (true)

    if leaks printed

  • (false)

    otherwise


94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/metasploit/framework/spec/constants/suite.rb', line 94

def self.print_leaked_constants(hook)
  hook_log_pathname = log_pathname(hook)

  leaks = false

  if hook_log_pathname.exist?
    leaks = true
    $stderr.puts "Leaked constants detected under #{Metasploit::Framework::Spec::Constants::PARENT_CONSTANT} #{hook} suite:"

    hook_log_pathname.open do |f|
      f.each_line do |line|
        constant_name = line.strip
        full_name = Metasploit::Framework::Spec::Constants.full_name(constant_name)

        if full_name
          formatted_full_name = " # #{full_name}"
        end

        $stderr.puts "  #{constant_name}#{formatted_full_name}"
      end
    end
  end

  leaks
end