bcpm 0.11
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +133 -0
- data/Manifest +23 -0
- data/README +292 -0
- data/Rakefile +23 -0
- data/bcpm.gemspec +35 -0
- data/bin/bcpm +5 -0
- data/lib/bcpm.rb +23 -0
- data/lib/bcpm/cleanup.rb +24 -0
- data/lib/bcpm/cli.rb +262 -0
- data/lib/bcpm/config.rb +100 -0
- data/lib/bcpm/dist.rb +133 -0
- data/lib/bcpm/duel.rb +153 -0
- data/lib/bcpm/git.rb +93 -0
- data/lib/bcpm/match.rb +275 -0
- data/lib/bcpm/player.rb +410 -0
- data/lib/bcpm/regen.rb +102 -0
- data/lib/bcpm/socket.rb +8 -0
- data/lib/bcpm/tests/assertion_error.rb +13 -0
- data/lib/bcpm/tests/assertions.rb +102 -0
- data/lib/bcpm/tests/case_base.rb +166 -0
- data/lib/bcpm/tests/environment.rb +256 -0
- data/lib/bcpm/tests/suite.rb +114 -0
- data/lib/bcpm/tests/test_match.rb +166 -0
- data/lib/bcpm/update.rb +38 -0
- metadata +129 -0
data/lib/bcpm/socket.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Bcpm
|
3
|
+
|
4
|
+
# :nodoc: namespace
|
5
|
+
module Tests
|
6
|
+
|
7
|
+
# Raised when a test assertion fails.
|
8
|
+
class AssertionError < RuntimeError
|
9
|
+
end # class Bcpm::Tests::AssertionError
|
10
|
+
|
11
|
+
end # namespace Bcpm::Tests
|
12
|
+
|
13
|
+
end # namespace Bcpm
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Bcpm
|
3
|
+
|
4
|
+
# :nodoc: namespace
|
5
|
+
module Tests
|
6
|
+
|
7
|
+
# Assertions for match tests.
|
8
|
+
module Assertions
|
9
|
+
# Fails unless the match was won.
|
10
|
+
def should_win
|
11
|
+
return if match.winner == :a
|
12
|
+
raise Bcpm::Tests::AssertionError, "Player was expected to win, but didn't! " + match.outcome
|
13
|
+
end
|
14
|
+
|
15
|
+
# Fails unless the match was won, and the Reason: line includes the argument text.
|
16
|
+
def should_win_by(reason)
|
17
|
+
should_win
|
18
|
+
|
19
|
+
return if match.reason.index(reason)
|
20
|
+
raise Bcpm::Tests::AssertionError, "Player was expected to win by #{reason} and didn't. " +
|
21
|
+
match.reason
|
22
|
+
end
|
23
|
+
|
24
|
+
# Fails unless the match was lost.
|
25
|
+
def should_lose
|
26
|
+
return if match.winner == :b
|
27
|
+
raise Bcpm::Tests::AssertionError, "Player was expected to lose, but didn't! " + match.outcome
|
28
|
+
end
|
29
|
+
|
30
|
+
# Fails unless the match was lost, and the Reason: line includes the argument text.
|
31
|
+
def should_lose_by(reason)
|
32
|
+
should_lose
|
33
|
+
|
34
|
+
return if match.reason.index(reason)
|
35
|
+
raise Bcpm::Tests::AssertionError, "Player was expected to win by #{reason} and didn't. " +
|
36
|
+
match.reason
|
37
|
+
end
|
38
|
+
|
39
|
+
# Fails if the player code threw any exception.
|
40
|
+
def should_not_throw
|
41
|
+
if match.output.index(/\n(\S*)Exception(.*?)\n\S/m)
|
42
|
+
raise Bcpm::Tests::AssertionError, "Player should not have thrown exceptions! " +
|
43
|
+
"It threw #{$1}Exception#{$2}"
|
44
|
+
end
|
45
|
+
if match.chatter.index(/\n(\S*)Exception(.*?)\n\S/m)
|
46
|
+
raise Bcpm::Tests::AssertionError, "Player should not have thrown exceptions! " +
|
47
|
+
"It threw #{$1}Exception#{$2}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Always fails. Useful for obtaining the game log.
|
52
|
+
def fail(reason = 'Test case called fail!')
|
53
|
+
raise Bcpm::Tests::AssertionError, reason
|
54
|
+
end
|
55
|
+
|
56
|
+
# Fails unless a unit's output matches the given regular expression.
|
57
|
+
#
|
58
|
+
# If a block is given, yields to the block for every match.
|
59
|
+
def should_match_unit_output(pattern)
|
60
|
+
matched = false
|
61
|
+
|
62
|
+
match.output_lines.each do |line|
|
63
|
+
next unless unit_output = _parse_unit_output(line)
|
64
|
+
if match = pattern.match(unit_output[:output])
|
65
|
+
matched = true
|
66
|
+
if Kernel.block_given?
|
67
|
+
yield unit_output, match
|
68
|
+
else
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
raise Bcpm::Tests::AssertionError, "No unit output matched #{pattern.inspect}!" unless matched
|
75
|
+
end
|
76
|
+
|
77
|
+
# Parses a unit's console output (usually via System.out.print*).
|
78
|
+
#
|
79
|
+
# If the given line looks like a unit's console output, returns a hash with the following keys:
|
80
|
+
# :team:: 'A' or 'B' (should always be 'A', unless the case enables team B's console output)
|
81
|
+
# :unit_type:: e.g., 'ARCHON'
|
82
|
+
# :unit_id:: the robot ID of the unit who wrote the line, parsed as an Integer
|
83
|
+
# :round:: the round when the line was output, parsed as an Integer
|
84
|
+
# :output:: the (first line of the) string that the unit produced
|
85
|
+
#
|
86
|
+
# If the line doesn't parse out, returns nil.
|
87
|
+
def _parse_unit_output(line)
|
88
|
+
line_match = /^\[([AB])\:([A-Z]+)\#(\d+)\@(\d+)\](.*)$/.match line
|
89
|
+
return nil unless line_match
|
90
|
+
{
|
91
|
+
:team => line_match[1],
|
92
|
+
:unit_type => line_match[2],
|
93
|
+
:unit_id => line_match[3].to_i,
|
94
|
+
:round => line_match[4].to_i,
|
95
|
+
:output => line_match[5]
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end # namespace Bcpm::Tests
|
101
|
+
|
102
|
+
end # namespace Bcpm
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Bcpm
|
3
|
+
|
4
|
+
# :nodoc: namespace
|
5
|
+
module Tests
|
6
|
+
|
7
|
+
# Base class for test cases.
|
8
|
+
#
|
9
|
+
# Each test case is its own anonymous class.
|
10
|
+
class CaseBase
|
11
|
+
class <<self
|
12
|
+
# Called before any code is evaluated in the class context.
|
13
|
+
def _setup
|
14
|
+
@map = nil
|
15
|
+
@suite_map = false
|
16
|
+
@vs = nil
|
17
|
+
@side = :a
|
18
|
+
@match = nil
|
19
|
+
@options = {}
|
20
|
+
@env = Bcpm::Tests::Environment.new
|
21
|
+
@env_used = false
|
22
|
+
|
23
|
+
@tests = []
|
24
|
+
@environments = []
|
25
|
+
@matches = []
|
26
|
+
end
|
27
|
+
|
28
|
+
# Called after all code is evaluated in the class context.
|
29
|
+
def _post_eval
|
30
|
+
@environments << @env if @env_used
|
31
|
+
@env = nil
|
32
|
+
@options = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# Called by public methods before they change the environment.
|
36
|
+
def _env_change
|
37
|
+
if @env_used
|
38
|
+
@environments << @env
|
39
|
+
@env = Bcpm::Tests::Environment.new
|
40
|
+
@env_used = false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Set the map for following matches.
|
45
|
+
def map(map_name)
|
46
|
+
@map = map_name.dup.to_s
|
47
|
+
@suite_map = false
|
48
|
+
end
|
49
|
+
|
50
|
+
# Set the map for the following match. Use a map in the player's test suite.
|
51
|
+
def suite_map(map_name)
|
52
|
+
@map = map_name.dup.to_s
|
53
|
+
@suite_map = true
|
54
|
+
end
|
55
|
+
|
56
|
+
# Set the enemy for following matches.
|
57
|
+
def vs(player_name)
|
58
|
+
@vs = player_name.dup.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
# Set our side for the following matches.
|
62
|
+
def side(side)
|
63
|
+
@side = side.to_s.downcase.to_sym
|
64
|
+
end
|
65
|
+
|
66
|
+
# Set a simulation option.
|
67
|
+
def option(key, value)
|
68
|
+
key = Bcpm::Match.engine_options[key.to_s] if key.kind_of? Symbol
|
69
|
+
|
70
|
+
if value.nil?
|
71
|
+
@options.delete key
|
72
|
+
else
|
73
|
+
@options[key] = value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Replaces a class implementation file (.java) with another one (presumably from tests).
|
78
|
+
def replace_class(target, source)
|
79
|
+
_env_change
|
80
|
+
@env.file_op [:file, target, source]
|
81
|
+
end
|
82
|
+
|
83
|
+
# Replaces all fragments labeled with target_fragment in target_class with another fragment.
|
84
|
+
def replace_code(target_class, target_fragment, source_class, source_fragment)
|
85
|
+
_env_change
|
86
|
+
@env.file_op [:fragment, [target_class, target_fragment], [source_class, source_fragment]]
|
87
|
+
end
|
88
|
+
|
89
|
+
# Redirects all method calls using a method name to a static method.
|
90
|
+
#
|
91
|
+
# Assumes the target method is a member method, and passes "this" to the static method.
|
92
|
+
def stub_member_call(source, target)
|
93
|
+
_env_change
|
94
|
+
@env.patch_op [:stub_member, target, source]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Redirects all method calls using a method name to a static method.
|
98
|
+
#
|
99
|
+
# Assumes the target method is a static method, and ignores the call target.
|
100
|
+
def stub_static_call(source, target)
|
101
|
+
_env_change
|
102
|
+
@env.patch_op [:stub_static, target, source]
|
103
|
+
end
|
104
|
+
|
105
|
+
# Create a test match. The block contains test cases for the match.
|
106
|
+
def match(&block)
|
107
|
+
begin
|
108
|
+
@env_used = true
|
109
|
+
map = @suite_map ? @env.suite_map_path(@map) : @map
|
110
|
+
@match = Bcpm::Tests::TestMatch.new @side, @vs, map, @env, @options
|
111
|
+
self.class_eval(&block)
|
112
|
+
@matches << @match
|
113
|
+
ensure
|
114
|
+
@match = nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Create a test match.
|
119
|
+
def it(label, &block)
|
120
|
+
raise "it can only be called within match blocks!" if @match.nil?
|
121
|
+
@tests << self.new(label, @match, block)
|
122
|
+
end
|
123
|
+
|
124
|
+
# All the environments used in the tests.
|
125
|
+
attr_reader :environments
|
126
|
+
# All the matches used in the tests.
|
127
|
+
attr_reader :matches
|
128
|
+
# All test cases.
|
129
|
+
attr_reader :tests
|
130
|
+
end
|
131
|
+
|
132
|
+
# Descriptive label for the test case.
|
133
|
+
attr_reader :label
|
134
|
+
# Test match used by the test case.
|
135
|
+
attr_reader :match
|
136
|
+
|
137
|
+
# Called by match.
|
138
|
+
def initialize(label, match, block)
|
139
|
+
@label = label
|
140
|
+
@match = match
|
141
|
+
@block = block
|
142
|
+
end
|
143
|
+
|
144
|
+
# User-readable description of test conditions.
|
145
|
+
def description
|
146
|
+
"#{match.description} #{label}"
|
147
|
+
end
|
148
|
+
|
149
|
+
# Verifies the match output against the test case.
|
150
|
+
#
|
151
|
+
# Returns nil for success, or an AssertionError exception if the case failed.
|
152
|
+
def check_output
|
153
|
+
begin
|
154
|
+
self.instance_eval &@block
|
155
|
+
return nil
|
156
|
+
rescue Bcpm::Tests::AssertionError => e
|
157
|
+
return e
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
include Bcpm::Tests::Assertions
|
162
|
+
end # class Bcpm::Tests::CaseBase
|
163
|
+
|
164
|
+
end # namespace Bcpm::Tests
|
165
|
+
|
166
|
+
end # namespace Bcpm
|
@@ -0,0 +1,256 @@
|
|
1
|
+
require 'English'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
# :nodoc: namespace
|
6
|
+
module Bcpm
|
7
|
+
|
8
|
+
# :nodoc: namespace
|
9
|
+
module Tests
|
10
|
+
|
11
|
+
# A match run for simulation purposes.
|
12
|
+
#
|
13
|
+
# Each test case is its own anonymous class.
|
14
|
+
class Environment
|
15
|
+
# Name of the player container for the enviornment.
|
16
|
+
attr_reader :player_name
|
17
|
+
|
18
|
+
# The build log, if the build happened.
|
19
|
+
attr_reader :build_log
|
20
|
+
|
21
|
+
# Creates a new environment blueprint.
|
22
|
+
#
|
23
|
+
# Args:
|
24
|
+
# prebuilt_name:: if given, the created blueprint points to an already-built environment
|
25
|
+
def initialize(prebuilt_name = nil)
|
26
|
+
@file_ops = []
|
27
|
+
@patch_ops = []
|
28
|
+
@build_log = nil
|
29
|
+
|
30
|
+
if prebuilt_name
|
31
|
+
@player_name = prebuilt_name
|
32
|
+
@available = true
|
33
|
+
else
|
34
|
+
@player_name = self.class.new_player_name
|
35
|
+
@available = false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Puts together an environment according to the blueprint.
|
40
|
+
def setup(suite_path)
|
41
|
+
return true if @available
|
42
|
+
|
43
|
+
begin
|
44
|
+
test_player = File.basename suite_path
|
45
|
+
|
46
|
+
@player_path = Bcpm::Player.checkpoint test_player, 'master', player_name
|
47
|
+
raise "Failed to checkpoint player at #{suite_path}" unless @player_path
|
48
|
+
@player_src = Bcpm::Player.package_path(@player_path)
|
49
|
+
|
50
|
+
file_ops
|
51
|
+
patch_ops
|
52
|
+
|
53
|
+
unless build
|
54
|
+
print "Test environment build failed! Some tests will not run!\n"
|
55
|
+
print "#{@build_log}\n"
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
rescue Exception => e
|
59
|
+
print "Failed setting up test environment! Some tests will not run!\n"
|
60
|
+
print "#{e.class.name}: #{e.to_s}\n#{e.backtrace.join("\n")}\n\n"
|
61
|
+
return false
|
62
|
+
end
|
63
|
+
@available = true
|
64
|
+
end
|
65
|
+
|
66
|
+
# True if the environment has been setup and can be used to run tests.
|
67
|
+
def available?
|
68
|
+
@available
|
69
|
+
end
|
70
|
+
|
71
|
+
# Undoes the effects of setup.
|
72
|
+
def teardown
|
73
|
+
Bcpm::Player.uninstall player_name
|
74
|
+
@available = false
|
75
|
+
end
|
76
|
+
|
77
|
+
# Path to the maps in the test suite for the player.
|
78
|
+
def suite_map_path(map_name)
|
79
|
+
File.join Bcpm::Player.local_root, player_name, 'suite', 'maps',
|
80
|
+
map_name + '.xml'
|
81
|
+
end
|
82
|
+
|
83
|
+
# Queue an operation that adds a file.
|
84
|
+
def file_op(op)
|
85
|
+
@file_ops << op
|
86
|
+
end
|
87
|
+
|
88
|
+
# Queue an operation that patches all source files.
|
89
|
+
def patch_op(op)
|
90
|
+
@patch_ops << op
|
91
|
+
end
|
92
|
+
|
93
|
+
# Copies files from the test suite to the environment.
|
94
|
+
#
|
95
|
+
# Called by setup, uses its environment.
|
96
|
+
def file_ops
|
97
|
+
@file_ops.each do |op|
|
98
|
+
op_type, target, source = *op
|
99
|
+
|
100
|
+
if op_type == :fragment
|
101
|
+
target, target_fragment = *target
|
102
|
+
source, source_fragment = *source
|
103
|
+
end
|
104
|
+
|
105
|
+
target = "#{@player_name}.#{target}"
|
106
|
+
source = "#{@player_name}.#{source}"
|
107
|
+
file_path = java_path @player_src, source
|
108
|
+
|
109
|
+
next unless File.exist? file_path
|
110
|
+
source_contents = File.read file_path
|
111
|
+
|
112
|
+
case op_type
|
113
|
+
when :file
|
114
|
+
contents = source_contents
|
115
|
+
when :fragment
|
116
|
+
next unless fragment_match = fragment_regexp(source_fragment).match(source_contents)
|
117
|
+
contents = fragment_match[0]
|
118
|
+
end
|
119
|
+
|
120
|
+
source_pkg = java_package source
|
121
|
+
target_pkg = java_package target
|
122
|
+
unless source_pkg == target_pkg
|
123
|
+
contents.gsub! /(^|[^A-Za-z0-9_.])#{source_pkg}([^A-Za-z0-9_]|$)/, "\\1#{target_pkg}\\2"
|
124
|
+
end
|
125
|
+
|
126
|
+
source_class = java_class source
|
127
|
+
target_class = java_class target
|
128
|
+
unless source_class == target_class
|
129
|
+
contents.gsub! /(^|[^A-Za-z0-9_])#{source_class}([^A-Za-z0-9_]|$)/, "\\1#{target_class}\\2"
|
130
|
+
end
|
131
|
+
|
132
|
+
file_path = java_path @player_src, target
|
133
|
+
|
134
|
+
case op_type
|
135
|
+
when :file
|
136
|
+
next unless File.exist?(File.dirname(file_path))
|
137
|
+
when :fragment
|
138
|
+
next unless File.exist?(file_path)
|
139
|
+
source_contents = File.read file_path
|
140
|
+
# Not using a string because source code might contain \1 which would confuse gsub.
|
141
|
+
source_contents.gsub! fragment_regexp(target_fragment) do |match|
|
142
|
+
"#{$1}\n#{contents}\n#{$3}"
|
143
|
+
end
|
144
|
+
contents = source_contents
|
145
|
+
end
|
146
|
+
|
147
|
+
File.open(file_path, 'wb') { |f| f.write contents }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Applies the patch operations to the source code in the environment.
|
152
|
+
#
|
153
|
+
# Called by setup, uses its environment.
|
154
|
+
def patch_ops
|
155
|
+
return if @patch_ops.empty?
|
156
|
+
|
157
|
+
Dir.glob(File.join(@player_src, '**', '*.java')).each do |file|
|
158
|
+
old_contents = File.read file
|
159
|
+
lines = old_contents.split("\n")
|
160
|
+
|
161
|
+
stubs_enabled = true
|
162
|
+
|
163
|
+
0.upto(lines.count - 1) do |i|
|
164
|
+
line = lines[i]
|
165
|
+
if directive_match = /^\s*\/\/\$(.*)$/.match(line)
|
166
|
+
directive = directive_match[1]
|
167
|
+
case directive.strip.downcase
|
168
|
+
when '+stubs', '-stubs'
|
169
|
+
stubs_enabled = directive[0] == ?+
|
170
|
+
end
|
171
|
+
else
|
172
|
+
@patch_ops.each do |op|
|
173
|
+
op_type, target, source = *op
|
174
|
+
|
175
|
+
case op_type
|
176
|
+
when :stub_member
|
177
|
+
if stubs_enabled
|
178
|
+
line.gsub!(/(^|[^A-Za-z0-9_.])([A-Za-z0-9_.]*\.)?#{source}\(/) do |match|
|
179
|
+
arg = ($2.nil? || $2.empty?) ? 'this' : $2[0..-2]
|
180
|
+
"#{$1}#{player_name}.#{target}(#{arg}, "
|
181
|
+
end
|
182
|
+
end
|
183
|
+
when :stub_static
|
184
|
+
if stubs_enabled
|
185
|
+
line.gsub! /(^|[^A-Za-z0-9_.])([A-Za-z0-9_.]*\.)?#{source}\(/,
|
186
|
+
"\\1#{player_name}.#{target}("
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
contents = lines.join("\n")
|
193
|
+
File.open(file, 'wb') { |f| f.write contents } unless contents == old_contents
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Builds the binaries for the player in this environment.
|
198
|
+
#
|
199
|
+
# Called by setup, uses its environment.
|
200
|
+
#
|
201
|
+
# Returns true for success, false for failure.
|
202
|
+
def build
|
203
|
+
uid = "bcpmbuild_#{Socket.hostname}_#{(Time.now.to_f * 1000).to_i}_#{$PID}"
|
204
|
+
tempdir = File.expand_path File.join(Dir.tmpdir, 'bcpm', uid)
|
205
|
+
FileUtils.mkdir_p tempdir
|
206
|
+
build_log = File.join tempdir, 'build.log'
|
207
|
+
build_file = File.join tempdir, 'build.xml'
|
208
|
+
Bcpm::Match.write_build build_file, 'bc.conf'
|
209
|
+
|
210
|
+
Bcpm::Match.run_build_script tempdir, build_file, build_log, 'build'
|
211
|
+
@build_log = File.exist?(build_log) ? File.open(build_log, 'rb') { |f| f.read } : ''
|
212
|
+
FileUtils.rm_rf tempdir
|
213
|
+
|
214
|
+
@build_log.index("\nBUILD SUCCESSFUL\n") ? true : false
|
215
|
+
end
|
216
|
+
|
217
|
+
# Regular expression matching a code fragment.
|
218
|
+
#
|
219
|
+
# The expression captures three groups: the fragment start marker, the fragment, and the fragment
|
220
|
+
# end marker.
|
221
|
+
def fragment_regexp(label)
|
222
|
+
/^([ \t]*\/\/\$[ \t]*\+mark\:[ \t]*#{label}\s)(.*)(\n[ \t]*\/\/\$[ \t]*\-mark\:[ \t]*#{label}\s)/m
|
223
|
+
end
|
224
|
+
|
225
|
+
# Path to .java source for a class.
|
226
|
+
def java_path(package_path, class_name)
|
227
|
+
File.join(File.dirname(package_path), class_name.gsub('.', '/') + '.java')
|
228
|
+
end
|
229
|
+
|
230
|
+
# Package for a Java class given its fully qualified name.
|
231
|
+
def java_package(class_name)
|
232
|
+
index = class_name.rindex '.'
|
233
|
+
index ? class_name[0, index] : ''
|
234
|
+
end
|
235
|
+
|
236
|
+
# Short class name for a Java class given its fully qualified name.
|
237
|
+
def java_class(class_name)
|
238
|
+
index = class_name.rindex '.'
|
239
|
+
index ? class_name[index + 1, class_name.length - index - 1] : class_name
|
240
|
+
end
|
241
|
+
|
242
|
+
# A player name guaranteed to be unique across the systme.
|
243
|
+
def self.new_player_name
|
244
|
+
# NOTE: Java doesn't like .s in it package names :)
|
245
|
+
host = Socket.hostname.gsub(/[^A-Za-z_]/, '_')
|
246
|
+
@prefix ||=
|
247
|
+
"bcpmtest_#{host}_#{(Time.now.to_f * 1000).to_i}_#{$PID}"
|
248
|
+
@counter ||= 0
|
249
|
+
@counter += 1
|
250
|
+
"#{@prefix}_#{@counter}"
|
251
|
+
end
|
252
|
+
end # class Bcpm::Tests::TestMatch
|
253
|
+
|
254
|
+
end # namespace Bcpm::Tests
|
255
|
+
|
256
|
+
end # namespace Bcpm
|