Guards

Version 28 (Brian Ford, 02/06/2009 04:24 PM)

1 1
h1. Guards
2 1
3 1
h2. Overview
4 1
5 9 Brian Ford
The RubySpec project intends to provide a complete and exhaustive specification of the Ruby language and its libraries.
6 9 Brian Ford
There is a single _standard_ implementation of Ruby. The _standard_ includes the stable, released versions available from http://ruby-lang.org. At present, the _standard_ is 1.8.6, 1.8.7, and 1.9.1. Collectively, the _standard_ is often referred to as MatzRuby or MRI.
7 9 Brian Ford
8 9 Brian Ford
The challenge for RubySpec is to correctly spec the different behaviors across versions, platforms, and implementations. To do this, the RubySpecs depend on _guards_ provided by MSpec. Guards are methods that may or may not take arguments and operate by yielding to a block if certain conditions are true. If the conditions for the guard are not true, the guard method does not yield to the block and the specs contained in the block are not run.
9 9 Brian Ford
10 11 Brian Ford
The guards serve two functions: 1) controlling which specs are run; 2) documenting the specs. The documentation function of the guards is as important for RubySpec as controlling which specs are run. Additionally, the guard structure itself was chosen to be visually and conceptually similar to the describe/it blocks in the specs.
11 1
12 12 Brian Ford
The guard blocks should be placed around @describe@ or @it@ blocks. Guards should _not_ be placed inside @it@ blocks. Guards should rarely, if ever, be placed inside @before@ or @after@ actions.
13 12 Brian Ford
14 11 Brian Ford
There are five categories of guards:
15 11 Brian Ford
16 9 Brian Ford
# versions
17 9 Brian Ford
# platforms
18 9 Brian Ford
# bugs
19 9 Brian Ford
# implementations
20 9 Brian Ford
# environments
21 9 Brian Ford
22 9 Brian Ford
The specific guards in each of these categories are explained below.
23 9 Brian Ford
24 9 Brian Ford
h3. 1. Versions
25 9 Brian Ford
26 26 Brian Ford
Different versions of Ruby have some methods that behave differently. That sounds circular, but it is the essence of software versions. To handle different version behaviors, MSpec provides the ruby_version_is guard.
27 9 Brian Ford
28 9 Brian Ford
29 9 Brian Ford
ruby_version_is "1.8.6.114" do
30 9 Brian Ford
  it "returns true" do
31 9 Brian Ford
  end
32 9 Brian Ford
end
33 9 Brian Ford
34 9 Brian Ford
ruby_version_is "1.8" .. "1.8.6" do
35 9 Brian Ford
  it "returns nil" do
36 9 Brian Ford
  end
37 9 Brian Ford
end
38 9 Brian Ford
39 9 Brian Ford
ruby_version_is "" ... "1.9" do
40 9 Brian Ford
  it "returns false" do
41 9 Brian Ford
  end
42 9 Brian Ford
end
43 9 Brian Ford
44 9 Brian Ford
45 10 Brian Ford
The ruby_version_is guard takes one argument that may be a string or a range of two strings. The format of the string is A.B.C.D, where:
46 10 Brian Ford
47 10 Brian Ford
* A is the major version
48 10 Brian Ford
* B is the minor version
49 10 Brian Ford
* C is the tiny (teeny) version
50 10 Brian Ford
* D is the patchlevel
51 10 Brian Ford
52 10 Brian Ford
The string is converted to a number on which comparisons between versions can be made so that, for instance, "1.8.6.37" is less than "1.8.6.112".
53 10 Brian Ford
54 10 Brian Ford
The range behaves as expected, respecting #exclude_end?. In the example above, "" ... "1.9" means every version *before* 1.9.
55 10 Brian Ford
56 9 Brian Ford
h3. 2. Platforms
57 1
58 11 Brian Ford
A single version of Ruby may have different behaviors depending on the platform on which it runs. MSpec provides several guards for these situations:
59 11 Brian Ford
60 11 Brian Ford
# big_endian
61 11 Brian Ford
# little_endian
62 11 Brian Ford
# platform_is
63 11 Brian Ford
# platform_is_not
64 9 Brian Ford
65 13 Brian Ford
The @big_endian@ guard yields to the block if the platform is big endian. Likewise for the @little_endian@ guard.
66 13 Brian Ford
67 13 Brian Ford
The platform_is and platform_is_not guards are more complex. As their names suggest, they are inverses.
68 13 Brian Ford
69 13 Brian Ford
70 13 Brian Ford
platform_is :linux, :bsd do
71 13 Brian Ford
  it "opens the file" do
72 13 Brian Ford
  end
73 13 Brian Ford
end
74 13 Brian Ford
75 13 Brian Ford
76 13 Brian Ford
The guard above will yield if RUBY_PLATFORM matches either "linux" OR "bsd".
77 13 Brian Ford
78 13 Brian Ford
79 13 Brian Ford
platform_is :linux, :wordsize => 32 do
80 13 Brian Ford
  it "opens the file" do
81 13 Brian Ford
  end
82 13 Brian Ford
end
83 13 Brian Ford
84 13 Brian Ford
85 13 Brian Ford
The guard above will yield if RUBY_PLATFORM matches "linux" AND the processor word size is 32-bit.
86 13 Brian Ford
87 13 Brian Ford
88 13 Brian Ford
platform_is_not :windows, :wordsize => 32 do
89 13 Brian Ford
  it "opens the file" do
90 13 Brian Ford
  end
91 13 Brian Ford
end
92 13 Brian Ford
93 13 Brian Ford
94 13 Brian Ford
The guard above will yield if RUBY_PLATFORM does not matches "windows" AND the processor word size is not 32-bit.
95 13 Brian Ford
96 13 Brian Ford
Special functionality exists for matching :windows and :java as platforms. For details, refer to the MSpec source.
97 13 Brian Ford
98 13 Brian Ford
99 14 Brian Ford
platform_is :os => [:darwin, :bsd] do
100 13 Brian Ford
  it "opens the file" do
101 13 Brian Ford
  end
102 13 Brian Ford
end
103 13 Brian Ford
104 13 Brian Ford
105 13 Brian Ford
The guard above will yield if Config::CONFIG['host_os'] matches either "darwin" OR "BSD". If Config::CONFIG['host_os'] is not set in rbconfig, the :os parameters will be matched against RUBY_PLATFORM instead.
106 13 Brian Ford
107 9 Brian Ford
h3. 3. Bugs
108 1
109 15 Brian Ford
Sometimes a bug is discovered in the _standard_. In this case, we do two things:
110 1
111 15 Brian Ford
# File a ticket on the "bug tracker":http://redmine.ruby-lang.org/ to find out if the suspected behavior is actually considered a bug.
112 15 Brian Ford
# Add a @ruby_bug@ guard that wraps the spec showing what is considered to be the _correct_ behavior.
113 2 Eero Saynatkari
114 15 Brian Ford
115 15 Brian Ford
ruby_bug "#5555", "1.8.6.114" do
116 15 Brian Ford
  it "returns the sum" do
117 15 Brian Ford
    (1 + 1).should == 2
118 7 Federico Builes
  end
119 6 Federico Builes
end
120 15 Brian Ford
121 6 Federico Builes
122 27 Brian Ford
The above guard will NOT yield to the block on any version of the _standard_ less than or equal to 1.8.6 patchlevel 114.
123 27 Brian Ford
124 15 Brian Ford
The @ruby_bug@ guard serves three purposes:
125 6 Federico Builes
126 15 Brian Ford
# It documents that there is a bug in a particular version of the standard and refers to the ticket for that bug.
127 15 Brian Ford
# It provides the correct behavior description for all implementations
128 15 Brian Ford
# It prevents the spec for the correct behavior from running (and failing) on versions of the _standard_ implementation that have the bug and have already been released.
129 27 Brian Ford
130 27 Brian Ford
If it is determined that the behavior is not a bug but rather a version difference, the @ruby_bug@ guard should be replaced by an appropriate ruby_version_is guard.
131 6 Federico Builes
132 15 Brian Ford
h3. 4. Implementations
133 2 Eero Saynatkari
134 19 Brian Ford
Ideally, all Ruby implementations would have exactly the same behaviors. This is not realistically possible given differences in the underlying technology, for instance, whether the process @fork@ facility is available.
135 19 Brian Ford
136 19 Brian Ford
It should be obvious, but it bears repeating, that in a specification for a standard, there should be an absolute minimum of incompatible behavior. These guards are not provided to make it easy to be inconsistent. Rather, they are provided to make specifying a single standard as simple as possible, recognizing that 100% conformity is an impossible ideal.
137 19 Brian Ford
138 28 Brian Ford
Recall that the purpose of the guards are _both_ to control which specs are run AND document the specs. There are four distinct situations covered by the five guards below. Each of the guards documents this difference.
139 19 Brian Ford
140 16 Brian Ford
# compliant_on
141 16 Brian Ford
# not_compliant_on
142 16 Brian Ford
# not_supported_on
143 16 Brian Ford
# deviates_on
144 16 Brian Ford
# extended_on
145 2 Eero Saynatkari
146 24 Brian Ford
Keep in mind that the arguments to these guards are communal property (as are all the specs) and respect them as you would want to be respected. There is no concept of opt-in or opt-out here. Every implementation is responsible for ensuring that their implementation's behavior is accurately represented in one of these compliance scenarios. If an implementation has an excessive number of non-compliant behaviors, this will be clearly visible in the specs.
147 24 Brian Ford
148 21 Brian Ford
h4. 4.1 compliant_on / not_compliant_on
149 21 Brian Ford
150 20 Brian Ford
The @compliant_on@ and not_compliant_on guards are inverses. They document that the enclosed spec passes or does not pass on the listed implementations or platforms.
151 19 Brian Ford
152 20 Brian Ford
153 20 Brian Ford
compliant_on :jruby, :rubinius do
154 20 Brian Ford
  it "returns true" do
155 20 Brian Ford
  end
156 20 Brian Ford
end
157 20 Brian Ford
158 15 Brian Ford
159 22 Brian Ford
The spec above will run ONLY on the _standard_, JRuby, or Rubinius. The @compliant_on@ guard will not run on any other implementation or platform except the ones listed.
160 1
161 21 Brian Ford
There is no need to list :ruby or :ruby19 for this guard. There is only one standard and specs are _always_ expected to run correctly on the _standard_. If there is version-specific behavior or a bug in the _standard_, use the @ruby_bug@ or ruby_version_is guard instead.
162 20 Brian Ford
163 20 Brian Ford
164 20 Brian Ford
not_compliant_on :rubinius do
165 20 Brian Ford
  it "returns false" do
166 20 Brian Ford
  end
167 20 Brian Ford
end
168 20 Brian Ford
169 20 Brian Ford
170 20 Brian Ford
The spec above will run on EVERY implementation except Rubinius.
171 20 Brian Ford
172 1
It should be obvious that the @compliant_on@ guard is more convenient when the number of implementations that conform to the standard is small compared to the total number of implementations. The not_compliant_on guard is useful when the number of non-conforming implementations is small.
173 1
174 22 Brian Ford
It is reasonable to assume that if there is a not_compliant_on guard for a particular implementation, then there is also a @deviates_on@ or @extended_on@ guarded spec for the same facet of behavior. This is not enforced in any way by the system of guards. It makes sense that if a facet of behavior is not consistent with the _standard_, then that can be illustrated by another spec. Perhaps that is not true in every case.
175 22 Brian Ford
176 21 Brian Ford
h4. 4.2 not_supported_on
177 21 Brian Ford
178 23 Brian Ford
The not_supported_on guard documents that there is no behavior like that described in the guarded spec for the listed implementations.
179 21 Brian Ford
180 21 Brian Ford
181 21 Brian Ford
not_supported_on :jruby do
182 21 Brian Ford
  it "forks the process" do
183 21 Brian Ford
  end
184 21 Brian Ford
end
185 21 Brian Ford
186 1
187 22 Brian Ford
The above spec will run on EVERY implementation _except_ JRuby.
188 22 Brian Ford
189 22 Brian Ford
The key difference between not_supported_on and the compliance guards above is that not_supported_on offers no alternative to the _standard_ behavior.
190 22 Brian Ford
191 21 Brian Ford
h4. 4.3 deviates_on
192 1
193 23 Brian Ford
If an implementation does not conform to the _standard_ behavior but instead offers an alternative behavior, the spec illustrating that is wrapped in a @deviates_on@ guard.
194 1
195 23 Brian Ford
196 23 Brian Ford
deviates_on :rubinius do
197 23 Brian Ford
  it "coerces to two Bignums" do
198 23 Brian Ford
  end
199 23 Brian Ford
end
200 23 Brian Ford
201 23 Brian Ford
202 23 Brian Ford
The above spec will run ONLY on Rubinius. More than one implementation can be listed.
203 23 Brian Ford
204 21 Brian Ford
h4. 4.4 extended_on
205 1
206 23 Brian Ford
If an implementation offers a behavior that does not exist at all in the standard, the spec illustrating that behavior is wrapped in an @extended_on@ guard.
207 23 Brian Ford
208 23 Brian Ford
209 23 Brian Ford
extended_on :rubinius do
210 23 Brian Ford
  it "returns an immutable vector" do
211 23 Brian Ford
  end
212 23 Brian Ford
end
213 23 Brian Ford
214 23 Brian Ford
215 23 Brian Ford
The above spec will run ONLY on Rubinius. More than one implementation can be listed.
216 20 Brian Ford
217 16 Brian Ford
h3. 5. Environments
218 18 Brian Ford
219 16 Brian Ford
The following guards are broadly grouped by their relation to how the specs are run. This is referred to as the spec _environment_. It includes guards for which runner (e.g. RSpec or MSpec) is executing the specs, which classes are loaded when the specs run, and whether the process running the specs has superuser privileges.
220 16 Brian Ford
221 16 Brian Ford
# runner_is
222 16 Brian Ford
# runner_is_not
223 1
# conflicts_with
224 20 Brian Ford
# as_superuser
225 1
# quarantine!
226 22 Brian Ford
227 22 Brian Ford
h4. 5.1 runner_is / runner_is_not
228 22 Brian Ford
229 25 Brian Ford
These two guards were initially added to protect specs whose behavior would change in the presence of certain standard library classes like Rational. This situation has been taken over by the @conflicts_with@ guard below. These guards should now only be used if the particular runner framework itself is an issue.
230 22 Brian Ford
231 25 Brian Ford
232 25 Brian Ford
runner_is :rspec do
233 25 Brian Ford
  it "does something that only runs under RSpec" do
234 25 Brian Ford
  end
235 25 Brian Ford
end
236 25 Brian Ford
237 25 Brian Ford
runner_is :mspec do
238 25 Brian Ford
  it "does something that only runs under MSpec" do
239 25 Brian Ford
  end
240 25 Brian Ford
end
241 25 Brian Ford
242 25 Brian Ford
243 25 Brian Ford
244 22 Brian Ford
h4. 5.2 conflicts_with
245 22 Brian Ford
246 25 Brian Ford
This guard wraps specs for methods whose behavior may be changed incompatibly by certain other classes.
247 22 Brian Ford
248 25 Brian Ford
249 25 Brian Ford
conflicts_with :SomeClass do
250 25 Brian Ford
  it "returns an Integer" do
251 25 Brian Ford
  end
252 25 Brian Ford
end
253 25 Brian Ford
254 25 Brian Ford
255 25 Brian Ford
The above guard will NOT yield if SomeClass is defined.
256 25 Brian Ford
257 22 Brian Ford
h4. 5.3 as_superuser
258 22 Brian Ford
259 25 Brian Ford
Some Ruby methods will only behave as expected if the process running the code example has superuser privileges.
260 25 Brian Ford
261 25 Brian Ford
262 25 Brian Ford
as_superuser do
263 25 Brian Ford
  describe "File.chown" do
264 25 Brian Ford
  end
265 25 Brian Ford
end
266 25 Brian Ford
267 25 Brian Ford
268 25 Brian Ford
The guard above will only yield if Process.euid == 0.
269 22 Brian Ford
270 22 Brian Ford
h4. 5.4 quarantine!
271 1
272 19 Brian Ford
The quarantine! guard will never yield to the block, so the specs inside the guard will not run. This guard is only used to temporarily disable a guard that is causing the _standard_ to fail severely (for example, by causing a segfault) AND the correctness of the spec is suspect. The guard allows the spec to be investigated but not cause any failures.
273 17 Brian Ford
274 17 Brian Ford
If a spec exposes a bug that is causing a segfault, the @ruby_bug@ guard should be used.
275 1
276 18 Brian Ford
277 17 Brian Ford
quarantine! do 
278 17 Brian Ford
  it "does something that causes a segfault" do
279 1
  end
280 17 Brian Ford
end
281 18 Brian Ford