Guards

Version 38 (Brian Ford, 02/10/2009 01:56 PM)

1 1
h1. Guards
2 1
3 1
h2. Overview
4 1
5 32 Brian Ford
The RubySpec project intends to provide a complete specification of the Ruby language and its libraries. 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.
6 9 Brian Ford
7 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.
8 9 Brian Ford
9 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.
10 1
11 38 Brian Ford
The guard blocks should be placed around @describe@ or @it@ blocks. Guards should _not_ be placed inside @it@ blocks. The @it@ description string should concisely describe the facet of behavior illustrated by the example code. If a guard is present, it necessarily means the guarded code behaves differently. That difference should be described in the @it@ string. In the case of @before@ or @after@ actions, use judgment to keep the specs concise but clear. If clarity would be enhanced by a few more lines of code, put the guards around the @before@ or @after@ action itself. Clarity always trumps counts of lines of code.
12 12 Brian Ford
13 11 Brian Ford
There are five categories of guards:
14 11 Brian Ford
15 9 Brian Ford
# versions
16 9 Brian Ford
# platforms
17 9 Brian Ford
# bugs
18 9 Brian Ford
# implementations
19 9 Brian Ford
# environments
20 9 Brian Ford
21 9 Brian Ford
The specific guards in each of these categories are explained below.
22 9 Brian Ford
23 9 Brian Ford
h3. 1. Versions
24 9 Brian Ford
25 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.
26 9 Brian Ford
27 9 Brian Ford
28 9 Brian Ford
ruby_version_is "1.8.6.114" do
29 9 Brian Ford
  it "returns true" do
30 9 Brian Ford
  end
31 9 Brian Ford
end
32 9 Brian Ford
33 9 Brian Ford
ruby_version_is "1.8" .. "1.8.6" do
34 9 Brian Ford
  it "returns nil" do
35 9 Brian Ford
  end
36 9 Brian Ford
end
37 9 Brian Ford
38 9 Brian Ford
ruby_version_is "" ... "1.9" do
39 9 Brian Ford
  it "returns false" do
40 9 Brian Ford
  end
41 9 Brian Ford
end
42 9 Brian Ford
43 9 Brian Ford
44 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:
45 10 Brian Ford
46 10 Brian Ford
* A is the major version
47 10 Brian Ford
* B is the minor version
48 10 Brian Ford
* C is the tiny (teeny) version
49 10 Brian Ford
* D is the patchlevel
50 10 Brian Ford
51 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".
52 10 Brian Ford
53 10 Brian Ford
The range behaves as expected, respecting #exclude_end?. In the example above, "" ... "1.9" means every version *before* 1.9.
54 10 Brian Ford
55 9 Brian Ford
h3. 2. Platforms
56 1
57 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:
58 11 Brian Ford
59 11 Brian Ford
# big_endian
60 11 Brian Ford
# little_endian
61 11 Brian Ford
# platform_is
62 11 Brian Ford
# platform_is_not
63 9 Brian Ford
64 13 Brian Ford
The @big_endian@ guard yields to the block if the platform is big endian. Likewise for the @little_endian@ guard.
65 13 Brian Ford
66 13 Brian Ford
The platform_is and platform_is_not guards are more complex. As their names suggest, they are inverses.
67 13 Brian Ford
68 13 Brian Ford
69 13 Brian Ford
platform_is :linux, :bsd do
70 13 Brian Ford
  it "opens the file" do
71 13 Brian Ford
  end
72 13 Brian Ford
end
73 13 Brian Ford
74 13 Brian Ford
75 13 Brian Ford
The guard above will yield if RUBY_PLATFORM matches either "linux" OR "bsd".
76 13 Brian Ford
77 13 Brian Ford
78 13 Brian Ford
platform_is :linux, :wordsize => 32 do
79 13 Brian Ford
  it "opens the file" do
80 13 Brian Ford
  end
81 13 Brian Ford
end
82 13 Brian Ford
83 13 Brian Ford
84 13 Brian Ford
The guard above will yield if RUBY_PLATFORM matches "linux" AND the processor word size is 32-bit.
85 13 Brian Ford
86 13 Brian Ford
87 13 Brian Ford
platform_is_not :windows, :wordsize => 32 do
88 13 Brian Ford
  it "opens the file" do
89 13 Brian Ford
  end
90 13 Brian Ford
end
91 13 Brian Ford
92 13 Brian Ford
93 13 Brian Ford
The guard above will yield if RUBY_PLATFORM does not matches "windows" AND the processor word size is not 32-bit.
94 13 Brian Ford
95 13 Brian Ford
Special functionality exists for matching :windows and :java as platforms. For details, refer to the MSpec source.
96 13 Brian Ford
97 13 Brian Ford
98 14 Brian Ford
platform_is :os => [:darwin, :bsd] do
99 13 Brian Ford
  it "opens the file" do
100 13 Brian Ford
  end
101 13 Brian Ford
end
102 13 Brian Ford
103 13 Brian Ford
104 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.
105 13 Brian Ford
106 9 Brian Ford
h3. 3. Bugs
107 1
108 15 Brian Ford
Sometimes a bug is discovered in the _standard_. In this case, we do two things:
109 1
110 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.
111 15 Brian Ford
# Add a @ruby_bug@ guard that wraps the spec showing what is considered to be the _correct_ behavior.
112 2 Eero Saynatkari
113 15 Brian Ford
114 15 Brian Ford
ruby_bug "#5555", "1.8.6.114" do
115 15 Brian Ford
  it "returns the sum" do
116 15 Brian Ford
    (1 + 1).should == 2
117 7 Federico Builes
  end
118 6 Federico Builes
end
119 15 Brian Ford
120 6 Federico Builes
121 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.
122 27 Brian Ford
123 15 Brian Ford
The @ruby_bug@ guard serves three purposes:
124 6 Federico Builes
125 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.
126 15 Brian Ford
# It provides the correct behavior description for all implementations
127 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.
128 27 Brian Ford
129 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.
130 6 Federico Builes
131 15 Brian Ford
h3. 4. Implementations
132 2 Eero Saynatkari
133 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.
134 19 Brian Ford
135 30 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.
136 19 Brian Ford
137 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.
138 19 Brian Ford
139 16 Brian Ford
# compliant_on
140 16 Brian Ford
# not_compliant_on
141 16 Brian Ford
# not_supported_on
142 16 Brian Ford
# deviates_on
143 16 Brian Ford
# extended_on
144 2 Eero Saynatkari
145 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.
146 24 Brian Ford
147 21 Brian Ford
h4. 4.1 compliant_on / not_compliant_on
148 21 Brian Ford
149 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.
150 19 Brian Ford
151 20 Brian Ford
152 20 Brian Ford
compliant_on :jruby, :rubinius do
153 20 Brian Ford
  it "returns true" do
154 20 Brian Ford
  end
155 20 Brian Ford
end
156 20 Brian Ford
157 15 Brian Ford
158 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.
159 1
160 36 Brian Ford
Note that this guard could prevent the guarded spec from running on an implementation that passes the spec. It is important to only use it in cases where the behavior is clearly supported only on the listed implementations. Generally, it is better to use not_compliant_on to explicitly blacklist implementations.
161 36 Brian Ford
162 34 Brian Ford
There is no need to list :ruby 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.
163 20 Brian Ford
164 20 Brian Ford
165 20 Brian Ford
not_compliant_on :rubinius do
166 20 Brian Ford
  it "returns false" do
167 20 Brian Ford
  end
168 20 Brian Ford
end
169 20 Brian Ford
170 20 Brian Ford
171 33 Brian Ford
The spec above will run on EVERY implementation except Rubinius. The not_compliant_on guard will always run on the _standard_.
172 20 Brian Ford
173 30 Brian Ford
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. Another way to look at this, @compliant_on@ essentially whitelists the compliant implementations, while not_compliant_on blacklists the non-comforming implementations.
174 1
175 35 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. This is not always the case. For instance, the _standard_ may call a particular method on an instance that an alternative implementation has no need to call. The alternative implementation may behave the same in every aspect except calling the method. In such case, the not_compliant_on guard on that one facet of behavior is sufficient. It does not enhance the specs to add a @deviates_on@ spec that merely creates a mock should_not_receive expectation. This obviously is in the gray line between implementation and interface.
176 22 Brian Ford
177 21 Brian Ford
h4. 4.2 not_supported_on
178 21 Brian Ford
179 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.
180 21 Brian Ford
181 21 Brian Ford
182 21 Brian Ford
not_supported_on :jruby do
183 21 Brian Ford
  it "forks the process" do
184 21 Brian Ford
  end
185 21 Brian Ford
end
186 21 Brian Ford
187 1
188 34 Brian Ford
The above spec will run on EVERY implementation _except_ JRuby. In particular, it will always run an the _standard_. Further, it will raise an exception if passed :ruby.
189 22 Brian Ford
190 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.
191 22 Brian Ford
192 21 Brian Ford
h4. 4.3 deviates_on
193 1
194 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.
195 1
196 23 Brian Ford
197 23 Brian Ford
deviates_on :rubinius do
198 23 Brian Ford
  it "coerces to two Bignums" do
199 23 Brian Ford
  end
200 23 Brian Ford
end
201 23 Brian Ford
202 23 Brian Ford
203 34 Brian Ford
The above spec will run ONLY on Rubinius. More than one implementation can be listed. This guard will NEVER run on the _standard_ and will raise an exception if passed :ruby.
204 23 Brian Ford
205 21 Brian Ford
h4. 4.4 extended_on
206 1
207 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.
208 23 Brian Ford
209 23 Brian Ford
210 23 Brian Ford
extended_on :rubinius do
211 23 Brian Ford
  it "returns an immutable vector" do
212 23 Brian Ford
  end
213 23 Brian Ford
end
214 23 Brian Ford
215 23 Brian Ford
216 34 Brian Ford
The above spec will run ONLY on Rubinius. More than one implementation can be listed. This guard will NEVER run on the _standard_ and will raise an exception if passed :ruby.
217 20 Brian Ford
218 16 Brian Ford
h3. 5. Environments
219 18 Brian Ford
220 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.
221 16 Brian Ford
222 16 Brian Ford
# runner_is
223 16 Brian Ford
# runner_is_not
224 1
# conflicts_with
225 20 Brian Ford
# as_superuser
226 1
# quarantine!
227 22 Brian Ford
228 22 Brian Ford
h4. 5.1 runner_is / runner_is_not
229 22 Brian Ford
230 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.
231 22 Brian Ford
232 25 Brian Ford
233 25 Brian Ford
runner_is :rspec do
234 25 Brian Ford
  it "does something that only runs under RSpec" do
235 25 Brian Ford
  end
236 25 Brian Ford
end
237 25 Brian Ford
238 25 Brian Ford
runner_is :mspec do
239 25 Brian Ford
  it "does something that only runs under MSpec" do
240 25 Brian Ford
  end
241 25 Brian Ford
end
242 25 Brian Ford
243 25 Brian Ford
244 25 Brian Ford
245 22 Brian Ford
h4. 5.2 conflicts_with
246 22 Brian Ford
247 25 Brian Ford
This guard wraps specs for methods whose behavior may be changed incompatibly by certain other classes.
248 22 Brian Ford
249 25 Brian Ford
250 25 Brian Ford
conflicts_with :SomeClass do
251 25 Brian Ford
  it "returns an Integer" do
252 25 Brian Ford
  end
253 25 Brian Ford
end
254 25 Brian Ford
255 25 Brian Ford
256 25 Brian Ford
The above guard will NOT yield if SomeClass is defined.
257 25 Brian Ford
258 22 Brian Ford
h4. 5.3 as_superuser
259 22 Brian Ford
260 25 Brian Ford
Some Ruby methods will only behave as expected if the process running the code example has superuser privileges.
261 25 Brian Ford
262 25 Brian Ford
263 25 Brian Ford
as_superuser do
264 25 Brian Ford
  describe "File.chown" do
265 25 Brian Ford
  end
266 25 Brian Ford
end
267 25 Brian Ford
268 25 Brian Ford
269 25 Brian Ford
The guard above will only yield if Process.euid == 0.
270 22 Brian Ford
271 22 Brian Ford
h4. 5.4 quarantine!
272 1
273 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.
274 17 Brian Ford
275 17 Brian Ford
If a spec exposes a bug that is causing a segfault, the @ruby_bug@ guard should be used.
276 1
277 18 Brian Ford
278 17 Brian Ford
quarantine! do 
279 17 Brian Ford
  it "does something that causes a segfault" do
280 1
  end
281 17 Brian Ford
end
282 18 Brian Ford