|
<!DOCTYPE html > |
|
<html> |
|
<!-- This was hacked together by hand after python-markdown output the bulk of it. |
|
Maybe someday I'll put together a better script to aut-generate this from markdown source. --> |
|
<head> |
|
<!-- github-markdown, plus github pygments --> |
|
<style type='text/css'> |
|
/* <![CDATA[ */ |
|
@font-face { |
|
font-family: octicons-link; |
|
src: url(data:font/woff;charset=utf-8;base64,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) format('woff'); |
|
} |
|
|
|
.markdown-body { |
|
-webkit-text-size-adjust: 100%; |
|
text-size-adjust: 100%; |
|
color: #333; |
|
font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; |
|
font-size: 16px; |
|
line-height: 1.6; |
|
word-wrap: break-word; |
|
} |
|
|
|
.markdown-body a { |
|
background-color: transparent; |
|
} |
|
|
|
.markdown-body a:active, |
|
.markdown-body a:hover { |
|
outline: 0; |
|
} |
|
|
|
.markdown-body strong { |
|
font-weight: bold; |
|
} |
|
|
|
.markdown-body h1 { |
|
font-size: 2em; |
|
margin: 0.67em 0; |
|
} |
|
|
|
.markdown-body img { |
|
border: 0; |
|
} |
|
|
|
.markdown-body hr { |
|
box-sizing: content-box; |
|
height: 0; |
|
} |
|
|
|
.markdown-body pre { |
|
overflow: auto; |
|
} |
|
|
|
.markdown-body code, |
|
.markdown-body kbd, |
|
.markdown-body pre { |
|
font-family: monospace, monospace; |
|
font-size: 1em; |
|
} |
|
|
|
.markdown-body input { |
|
color: inherit; |
|
font: inherit; |
|
margin: 0; |
|
} |
|
|
|
.markdown-body html input[disabled] { |
|
cursor: default; |
|
} |
|
|
|
.markdown-body input { |
|
line-height: normal; |
|
} |
|
|
|
.markdown-body input[type="checkbox"] { |
|
box-sizing: border-box; |
|
padding: 0; |
|
} |
|
|
|
.markdown-body table { |
|
border-collapse: collapse; |
|
border-spacing: 0; |
|
} |
|
|
|
.markdown-body td, |
|
.markdown-body th { |
|
padding: 0; |
|
} |
|
|
|
.markdown-body * { |
|
box-sizing: border-box; |
|
} |
|
|
|
.markdown-body input { |
|
font: 13px / 1.4 Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; |
|
} |
|
|
|
.markdown-body a { |
|
color: #4078c0; |
|
text-decoration: none; |
|
} |
|
|
|
.markdown-body a:hover, |
|
.markdown-body a:active { |
|
text-decoration: underline; |
|
} |
|
|
|
.markdown-body hr { |
|
height: 0; |
|
margin: 15px 0; |
|
overflow: hidden; |
|
background: transparent; |
|
border: 0; |
|
border-bottom: 1px solid #ddd; |
|
} |
|
|
|
.markdown-body hr:before { |
|
display: table; |
|
content: ""; |
|
} |
|
|
|
.markdown-body hr:after { |
|
display: table; |
|
clear: both; |
|
content: ""; |
|
} |
|
|
|
.markdown-body h1, |
|
.markdown-body h2, |
|
.markdown-body h3, |
|
.markdown-body h4, |
|
.markdown-body h5, |
|
.markdown-body h6 { |
|
margin-top: 15px; |
|
margin-bottom: 15px; |
|
line-height: 1.1; |
|
} |
|
|
|
.markdown-body h1 { |
|
font-size: 30px; |
|
} |
|
|
|
.markdown-body h2 { |
|
font-size: 21px; |
|
} |
|
|
|
.markdown-body h3 { |
|
font-size: 16px; |
|
} |
|
|
|
.markdown-body h4 { |
|
font-size: 14px; |
|
} |
|
|
|
.markdown-body h5 { |
|
font-size: 12px; |
|
} |
|
|
|
.markdown-body h6 { |
|
font-size: 11px; |
|
} |
|
|
|
.markdown-body blockquote { |
|
margin: 0; |
|
} |
|
|
|
.markdown-body ul, |
|
.markdown-body ol { |
|
padding: 0; |
|
margin-top: 0; |
|
margin-bottom: 0; |
|
} |
|
|
|
.markdown-body ol ol, |
|
.markdown-body ul ol { |
|
list-style-type: lower-roman; |
|
} |
|
|
|
.markdown-body ul ul ol, |
|
.markdown-body ul ol ol, |
|
.markdown-body ol ul ol, |
|
.markdown-body ol ol ol { |
|
list-style-type: lower-alpha; |
|
} |
|
|
|
.markdown-body dd { |
|
margin-left: 0; |
|
} |
|
|
|
.markdown-body code { |
|
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; |
|
font-size: 12px; |
|
} |
|
|
|
.markdown-body pre { |
|
margin-top: 0; |
|
margin-bottom: 0; |
|
font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace; |
|
} |
|
|
|
.markdown-body .select::-ms-expand { |
|
opacity: 0; |
|
} |
|
|
|
.markdown-body .octicon { |
|
font: normal normal normal 16px/1 octicons-link; |
|
display: inline-block; |
|
text-decoration: none; |
|
text-rendering: auto; |
|
-webkit-font-smoothing: antialiased; |
|
-moz-osx-font-smoothing: grayscale; |
|
-webkit-user-select: none; |
|
-moz-user-select: none; |
|
-ms-user-select: none; |
|
user-select: none; |
|
} |
|
|
|
.markdown-body .octicon-link:before { |
|
content: '\f05c'; |
|
} |
|
|
|
.markdown-body:before { |
|
display: table; |
|
content: ""; |
|
} |
|
|
|
.markdown-body:after { |
|
display: table; |
|
clear: both; |
|
content: ""; |
|
} |
|
|
|
.markdown-body>*:first-child { |
|
margin-top: 0 !important; |
|
} |
|
|
|
.markdown-body>*:last-child { |
|
margin-bottom: 0 !important; |
|
} |
|
|
|
.markdown-body a:not([href]) { |
|
color: inherit; |
|
text-decoration: none; |
|
} |
|
|
|
.markdown-body .anchor { |
|
display: inline-block; |
|
padding-right: 2px; |
|
margin-left: -18px; |
|
} |
|
|
|
.markdown-body .anchor:focus { |
|
outline: none; |
|
} |
|
|
|
.markdown-body h1, |
|
.markdown-body h2, |
|
.markdown-body h3, |
|
.markdown-body h4, |
|
.markdown-body h5, |
|
.markdown-body h6 { |
|
margin-top: 1em; |
|
margin-bottom: 16px; |
|
font-weight: bold; |
|
line-height: 1.4; |
|
} |
|
|
|
.markdown-body h1 .octicon-link, |
|
.markdown-body h2 .octicon-link, |
|
.markdown-body h3 .octicon-link, |
|
.markdown-body h4 .octicon-link, |
|
.markdown-body h5 .octicon-link, |
|
.markdown-body h6 .octicon-link { |
|
color: #000; |
|
vertical-align: middle; |
|
visibility: hidden; |
|
} |
|
|
|
.markdown-body h1:hover .anchor, |
|
.markdown-body h2:hover .anchor, |
|
.markdown-body h3:hover .anchor, |
|
.markdown-body h4:hover .anchor, |
|
.markdown-body h5:hover .anchor, |
|
.markdown-body h6:hover .anchor { |
|
text-decoration: none; |
|
} |
|
|
|
.markdown-body h1:hover .anchor .octicon-link, |
|
.markdown-body h2:hover .anchor .octicon-link, |
|
.markdown-body h3:hover .anchor .octicon-link, |
|
.markdown-body h4:hover .anchor .octicon-link, |
|
.markdown-body h5:hover .anchor .octicon-link, |
|
.markdown-body h6:hover .anchor .octicon-link { |
|
visibility: visible; |
|
} |
|
|
|
.markdown-body h1 { |
|
padding-bottom: 0.3em; |
|
font-size: 2.25em; |
|
line-height: 1.2; |
|
border-bottom: 1px solid #eee; |
|
} |
|
|
|
.markdown-body h1 .anchor { |
|
line-height: 1; |
|
} |
|
|
|
.markdown-body h2 { |
|
padding-bottom: 0.3em; |
|
font-size: 1.75em; |
|
line-height: 1.225; |
|
border-bottom: 1px solid #eee; |
|
} |
|
|
|
.markdown-body h2 .anchor { |
|
line-height: 1; |
|
} |
|
|
|
.markdown-body h3 { |
|
font-size: 1.5em; |
|
line-height: 1.43; |
|
} |
|
|
|
.markdown-body h3 .anchor { |
|
line-height: 1.2; |
|
} |
|
|
|
.markdown-body h4 { |
|
font-size: 1.25em; |
|
} |
|
|
|
.markdown-body h4 .anchor { |
|
line-height: 1.2; |
|
} |
|
|
|
.markdown-body h5 { |
|
font-size: 1em; |
|
} |
|
|
|
.markdown-body h5 .anchor { |
|
line-height: 1.1; |
|
} |
|
|
|
.markdown-body h6 { |
|
font-size: 1em; |
|
color: #777; |
|
} |
|
|
|
.markdown-body h6 .anchor { |
|
line-height: 1.1; |
|
} |
|
|
|
.markdown-body p, |
|
.markdown-body blockquote, |
|
.markdown-body ul, |
|
.markdown-body ol, |
|
.markdown-body dl, |
|
.markdown-body table, |
|
.markdown-body pre { |
|
margin-top: 0; |
|
margin-bottom: 16px; |
|
} |
|
|
|
.markdown-body hr { |
|
height: 4px; |
|
padding: 0; |
|
margin: 16px 0; |
|
background-color: #e7e7e7; |
|
border: 0 none; |
|
} |
|
|
|
.markdown-body ul, |
|
.markdown-body ol { |
|
padding-left: 2em; |
|
} |
|
|
|
.markdown-body ul ul, |
|
.markdown-body ul ol, |
|
.markdown-body ol ol, |
|
.markdown-body ol ul { |
|
margin-top: 0; |
|
margin-bottom: 0; |
|
} |
|
|
|
.markdown-body li>p { |
|
margin-top: 16px; |
|
} |
|
|
|
.markdown-body dl { |
|
padding: 0; |
|
} |
|
|
|
.markdown-body dl dt { |
|
padding: 0; |
|
margin-top: 16px; |
|
font-size: 1em; |
|
font-style: italic; |
|
font-weight: bold; |
|
} |
|
|
|
.markdown-body dl dd { |
|
padding: 0 16px; |
|
margin-bottom: 16px; |
|
} |
|
|
|
.markdown-body blockquote { |
|
padding: 0 15px; |
|
color: #777; |
|
border-left: 4px solid #ddd; |
|
} |
|
|
|
.markdown-body blockquote>:first-child { |
|
margin-top: 0; |
|
} |
|
|
|
.markdown-body blockquote>:last-child { |
|
margin-bottom: 0; |
|
} |
|
|
|
.markdown-body table { |
|
display: block; |
|
width: 100%; |
|
overflow: auto; |
|
word-break: normal; |
|
word-break: keep-all; |
|
} |
|
|
|
.markdown-body table th { |
|
font-weight: bold; |
|
} |
|
|
|
.markdown-body table th, |
|
.markdown-body table td { |
|
padding: 6px 13px; |
|
border: 1px solid #ddd; |
|
} |
|
|
|
.markdown-body table tr { |
|
background-color: #fff; |
|
border-top: 1px solid #ccc; |
|
} |
|
|
|
.markdown-body table tr:nth-child(2n) { |
|
background-color: #f8f8f8; |
|
} |
|
|
|
.markdown-body img { |
|
max-width: 100%; |
|
box-sizing: content-box; |
|
background-color: #fff; |
|
} |
|
|
|
.markdown-body code { |
|
padding: 0; |
|
padding-top: 0.2em; |
|
padding-bottom: 0.2em; |
|
margin: 0; |
|
font-size: 85%; |
|
background-color: rgba(0,0,0,0.04); |
|
border-radius: 3px; |
|
} |
|
|
|
.markdown-body code:before, |
|
.markdown-body code:after { |
|
letter-spacing: -0.2em; |
|
content: "\00a0"; |
|
} |
|
|
|
.markdown-body pre>code { |
|
padding: 0; |
|
margin: 0; |
|
font-size: 100%; |
|
word-break: normal; |
|
white-space: pre; |
|
background: transparent; |
|
border: 0; |
|
} |
|
|
|
.markdown-body .highlight { |
|
margin-bottom: 16px; |
|
} |
|
|
|
.markdown-body .highlight pre, |
|
.markdown-body pre { |
|
padding: 16px; |
|
overflow: auto; |
|
font-size: 85%; |
|
line-height: 1.45; |
|
background-color: #f7f7f7; |
|
border-radius: 3px; |
|
} |
|
|
|
.markdown-body .highlight pre { |
|
margin-bottom: 0; |
|
word-break: normal; |
|
} |
|
|
|
.markdown-body pre { |
|
word-wrap: normal; |
|
} |
|
|
|
.markdown-body pre code { |
|
display: inline; |
|
max-width: initial; |
|
padding: 0; |
|
margin: 0; |
|
overflow: initial; |
|
line-height: inherit; |
|
word-wrap: normal; |
|
background-color: transparent; |
|
border: 0; |
|
} |
|
|
|
.markdown-body pre code:before, |
|
.markdown-body pre code:after { |
|
content: normal; |
|
} |
|
|
|
.markdown-body kbd { |
|
display: inline-block; |
|
padding: 3px 5px; |
|
font-size: 11px; |
|
line-height: 10px; |
|
color: #555; |
|
vertical-align: middle; |
|
background-color: #fcfcfc; |
|
border: solid 1px #ccc; |
|
border-bottom-color: #bbb; |
|
border-radius: 3px; |
|
box-shadow: inset 0 -1px 0 #bbb; |
|
} |
|
|
|
.markdown-body .pl-c { |
|
color: #969896; |
|
} |
|
|
|
.markdown-body .pl-c1, |
|
.markdown-body .pl-s .pl-v { |
|
color: #0086b3; |
|
} |
|
|
|
.markdown-body .pl-e, |
|
.markdown-body .pl-en { |
|
color: #795da3; |
|
} |
|
|
|
.markdown-body .pl-s .pl-s1, |
|
.markdown-body .pl-smi { |
|
color: #333; |
|
} |
|
|
|
.markdown-body .pl-ent { |
|
color: #63a35c; |
|
} |
|
|
|
.markdown-body .pl-k { |
|
color: #a71d5d; |
|
} |
|
|
|
.markdown-body .pl-pds, |
|
.markdown-body .pl-s, |
|
.markdown-body .pl-s .pl-pse .pl-s1, |
|
.markdown-body .pl-sr, |
|
.markdown-body .pl-sr .pl-cce, |
|
.markdown-body .pl-sr .pl-sra, |
|
.markdown-body .pl-sr .pl-sre { |
|
color: #183691; |
|
} |
|
|
|
.markdown-body .pl-v { |
|
color: #ed6a43; |
|
} |
|
|
|
.markdown-body .pl-id { |
|
color: #b52a1d; |
|
} |
|
|
|
.markdown-body .pl-ii { |
|
background-color: #b52a1d; |
|
color: #f8f8f8; |
|
} |
|
|
|
.markdown-body .pl-sr .pl-cce { |
|
color: #63a35c; |
|
font-weight: bold; |
|
} |
|
|
|
.markdown-body .pl-ml { |
|
color: #693a17; |
|
} |
|
|
|
.markdown-body .pl-mh, |
|
.markdown-body .pl-mh .pl-en, |
|
.markdown-body .pl-ms { |
|
color: #1d3e81; |
|
font-weight: bold; |
|
} |
|
|
|
.markdown-body .pl-mq { |
|
color: #008080; |
|
} |
|
|
|
.markdown-body .pl-mi { |
|
color: #333; |
|
font-style: italic; |
|
} |
|
|
|
.markdown-body .pl-mb { |
|
color: #333; |
|
font-weight: bold; |
|
} |
|
|
|
.markdown-body .pl-md { |
|
background-color: #ffecec; |
|
color: #bd2c00; |
|
} |
|
|
|
.markdown-body .pl-mi1 { |
|
background-color: #eaffea; |
|
color: #55a532; |
|
} |
|
|
|
.markdown-body .pl-mdr { |
|
color: #795da3; |
|
font-weight: bold; |
|
} |
|
|
|
.markdown-body .pl-mo { |
|
color: #1d3e81; |
|
} |
|
|
|
.markdown-body kbd { |
|
display: inline-block; |
|
padding: 3px 5px; |
|
font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace; |
|
line-height: 10px; |
|
color: #555; |
|
vertical-align: middle; |
|
background-color: #fcfcfc; |
|
border: solid 1px #ccc; |
|
border-bottom-color: #bbb; |
|
border-radius: 3px; |
|
box-shadow: inset 0 -1px 0 #bbb; |
|
} |
|
|
|
.markdown-body .task-list-item { |
|
list-style-type: none; |
|
} |
|
|
|
.markdown-body .task-list-item+.task-list-item { |
|
margin-top: 3px; |
|
} |
|
|
|
.markdown-body .task-list-item input { |
|
margin: 0 0.35em 0.25em -1.6em; |
|
vertical-align: middle; |
|
} |
|
|
|
.markdown-body :checked+.radio-label { |
|
z-index: 1; |
|
position: relative; |
|
border-color: #4078c0; |
|
} |
|
|
|
.codehilite .hll { background-color: #ffffcc } |
|
.codehilite .c { color: #999988; font-style: italic } /* Comment */ |
|
.codehilite .err { color: #a61717; background-color: #e3d2d2 } /* Error */ |
|
.codehilite .k { color: #000000; font-weight: bold } /* Keyword */ |
|
.codehilite .o { color: #000000; font-weight: bold } /* Operator */ |
|
.codehilite .cm { color: #999988; font-style: italic } /* Comment.Multiline */ |
|
.codehilite .cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ |
|
.codehilite .c1 { color: #999988; font-style: italic } /* Comment.Single */ |
|
.codehilite .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ |
|
.codehilite .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ |
|
.codehilite .ge { color: #000000; font-style: italic } /* Generic.Emph */ |
|
.codehilite .gr { color: #aa0000 } /* Generic.Error */ |
|
.codehilite .gh { color: #999999 } /* Generic.Heading */ |
|
.codehilite .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ |
|
.codehilite .go { color: #888888 } /* Generic.Output */ |
|
.codehilite .gp { color: #555555 } /* Generic.Prompt */ |
|
.codehilite .gs { font-weight: bold } /* Generic.Strong */ |
|
.codehilite .gu { color: #aaaaaa } /* Generic.Subheading */ |
|
.codehilite .gt { color: #aa0000 } /* Generic.Traceback */ |
|
.codehilite .kc { color: #000000; font-weight: bold } /* Keyword.Constant */ |
|
.codehilite .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ |
|
.codehilite .kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ |
|
.codehilite .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ |
|
.codehilite .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ |
|
.codehilite .kt { color: #445588; font-weight: bold } /* Keyword.Type */ |
|
.codehilite .m { color: #009999 } /* Literal.Number */ |
|
.codehilite .s { color: #d01040 } /* Literal.String */ |
|
.codehilite .na { color: #008080 } /* Name.Attribute */ |
|
.codehilite .nb { color: #0086B3 } /* Name.Builtin */ |
|
.codehilite .nc { color: #445588; font-weight: bold } /* Name.Class */ |
|
.codehilite .no { color: #008080 } /* Name.Constant */ |
|
.codehilite .nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ |
|
.codehilite .ni { color: #800080 } /* Name.Entity */ |
|
.codehilite .ne { color: #990000; font-weight: bold } /* Name.Exception */ |
|
.codehilite .nf { color: #990000; font-weight: bold } /* Name.Function */ |
|
.codehilite .nl { color: #990000; font-weight: bold } /* Name.Label */ |
|
.codehilite .nn { color: #555555 } /* Name.Namespace */ |
|
.codehilite .nt { color: #000080 } /* Name.Tag */ |
|
.codehilite .nv { color: #008080 } /* Name.Variable */ |
|
.codehilite .ow { color: #000000; font-weight: bold } /* Operator.Word */ |
|
.codehilite .w { color: #bbbbbb } /* Text.Whitespace */ |
|
.codehilite .mf { color: #009999 } /* Literal.Number.Float */ |
|
.codehilite .mh { color: #009999 } /* Literal.Number.Hex */ |
|
.codehilite .mi { color: #009999 } /* Literal.Number.Integer */ |
|
.codehilite .mo { color: #009999 } /* Literal.Number.Oct */ |
|
.codehilite .sb { color: #d01040 } /* Literal.String.Backtick */ |
|
.codehilite .sc { color: #d01040 } /* Literal.String.Char */ |
|
.codehilite .sd { color: #d01040 } /* Literal.String.Doc */ |
|
.codehilite .s2 { color: #d01040 } /* Literal.String.Double */ |
|
.codehilite .se { color: #d01040 } /* Literal.String.Escape */ |
|
.codehilite .sh { color: #d01040 } /* Literal.String.Heredoc */ |
|
.codehilite .si { color: #d01040 } /* Literal.String.Interpol */ |
|
.codehilite .sx { color: #d01040 } /* Literal.String.Other */ |
|
.codehilite .sr { color: #009926 } /* Literal.String.Regex */ |
|
.codehilite .s1 { color: #d01040 } /* Literal.String.Single */ |
|
.codehilite .ss { color: #990073 } /* Literal.String.Symbol */ |
|
.codehilite .bp { color: #999999 } /* Name.Builtin.Pseudo */ |
|
.codehilite .vc { color: #008080 } /* Name.Variable.Class */ |
|
.codehilite .vg { color: #008080 } /* Name.Variable.Global */ |
|
.codehilite .vi { color: #008080 } /* Name.Variable.Instance */ |
|
.codehilite .il { color: #009999 } /* Literal.Number.Integer.Long */ |
|
|
|
|
|
/* ]]> */ |
|
</style> |
|
|
|
<style type='text/css'> |
|
/* <![CDATA[ */ |
|
.codehilite { |
|
page-break-inside: avoid; |
|
page-break-before: avoid; |
|
page-break-after: auto; |
|
break-inside: avoid; |
|
break-before: avoid; |
|
break-after: auto; |
|
} |
|
|
|
h1, h2, h3, h4, h5 { |
|
page-break-after: avoid; |
|
break-after: avoid; |
|
page-break-before: auto; |
|
break-before: auto; |
|
} |
|
|
|
section.no-break { |
|
page-break-inside: avoid; |
|
break-inside: avoid; |
|
} |
|
|
|
#content { |
|
column-count: 2; |
|
-moz-column-count: 2; |
|
-moz-column-gap: 2.5em; |
|
-moz-column-rule: 1px solid #eee; |
|
} |
|
|
|
.markdown-body h1 { |
|
border-bottom-color: black; |
|
} |
|
|
|
.markdown-body h2 { |
|
border-bottom: 4px solid black; |
|
} |
|
|
|
.markdown-body h3 { |
|
border-bottom: 2px solid black; |
|
} |
|
|
|
@media print { |
|
.markdown-body pre { |
|
background-color: #ddd; |
|
border: 1px solid #999; |
|
} |
|
|
|
#content { |
|
-moz-column-rule: 1px solid #333; |
|
} |
|
} |
|
|
|
/* ]]> */ |
|
</style> |
|
</head> |
|
<body class='markdown-body'> |
|
<h1>Serverspec and RSpec Cheat Sheet</h1> |
|
<div id='content'> |
|
<h2>Assertions</h2> |
|
<ul> |
|
<li><code>should</code> means "assert true".</li> |
|
<li><code>should_not</code> means "assert false".</li> |
|
</ul> |
|
<h2>Resources</h2> |
|
<p><a href="http://serverspec.org/resource_types.html">http://serverspec.org/resource_types.html</a></p> |
|
|
|
<section class='no-break'> |
|
<h3>file</h3> |
|
<p>Files, directories, and devices.</p> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="n">file</span><span class="p">(</span><span class="s1">'/path/to/file'</span><span class="p">)</span> <span class="k">do</span> |
|
|
|
<span class="c1">#INode type</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_file</span> <span class="p">}</span> <span class="c1"># or be_directory, or be_symlink</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">exist</span> <span class="p">}</span> |
|
|
|
<span class="c1">#Permissions</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_owned_by</span> <span class="s1">'username'</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_grouped_into</span> <span class="s1">'groupname'</span> <span class="p">}</span> |
|
|
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_mode</span> <span class="mi">440</span> <span class="p">}</span> |
|
|
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_readable</span> <span class="p">}</span> <span class="c1"># or be_writable or be_executable</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_readable</span><span class="o">.</span><span class="n">by</span><span class="p">(</span><span class="s1">'owner'</span><span class="p">)</span> <span class="p">}</span> <span class="c1"># or 'group' or 'others'</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_readable</span><span class="o">.</span><span class="n">by_user</span><span class="p">(</span><span class="s1">'username'</span><span class="p">)</span> <span class="p">}</span> |
|
|
|
<span class="c1">#Links</span> |
|
<span class="k">if</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_linked_to</span> <span class="s1">'/path/to/target'</span> <span class="p">}</span> |
|
|
|
<span class="c1">#Contents</span> |
|
<span class="n">its</span><span class="p">(</span><span class="ss">:md5sum</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="n">eq</span> <span class="s1">'...'</span> <span class="p">}</span> <span class="c1"># or, and rspec matcher</span> |
|
<span class="n">its</span><span class="p">(</span><span class="ss">:sha256sum</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="n">eq</span> <span class="s1">'...'</span> <span class="p">}</span> |
|
<span class="n">its</span><span class="p">(</span><span class="ss">:size</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="o"><</span> <span class="mi">1024</span> <span class="p">}</span> |
|
<span class="n">its</span><span class="p">(</span><span class="ss">:content</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="n">match</span> <span class="sr">/some pattern/</span> <span class="p">}</span> |
|
|
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>user</h3> |
|
<p>System users.</p> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="n">user</span><span class="p">(</span><span class="s1">'username'</span><span class="p">)</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">exist</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">belong_to_group</span> <span class="s1">'group'</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">have_home_directory</span> <span class="s1">'/home/username'</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">have_login_shell</span> <span class="s1">'/bin/bash'</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">have_authorized_key</span> <span class="s1">'ssh-rsa ABCD... user@hostname'</span> <span class="p">}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>group</h3> |
|
<p>System user groups.</p> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="n">group</span><span class="p">(</span><span class="s1">'groupname'</span><span class="p">)</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">exist</span> <span class="p">}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>packages</h3> |
|
<p>Software packages installed on the system.</p> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="n">package</span><span class="p">(</span><span class="s1">'httpd'</span><span class="p">)</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_installed</span> <span class="p">}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
|
|
|
|
<p>Select based on operating system:</p> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="n">package</span><span class="p">(</span><span class="s1">'httpd'</span><span class="p">),</span> <span class="ss">:if</span> <span class="o">=></span> <span class="n">os</span><span class="o">[</span><span class="ss">:family</span><span class="o">]</span> <span class="o">==</span> <span class="s1">'redhat'</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_installed</span> <span class="p">}</span> |
|
<span class="k">end</span> |
|
|
|
<span class="n">describe</span> <span class="n">package</span><span class="p">(</span><span class="s1">'apache2'</span><span class="p">),</span> <span class="ss">:if</span> <span class="o">=></span> <span class="n">os</span><span class="o">[</span><span class="ss">:family</span><span class="o">]</span> <span class="o">==</span> <span class="s1">'ubuntu'</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_installed</span> <span class="p">}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>port</h3> |
|
<p>Network ports</p> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="n">port</span><span class="p">(</span><span class="mi">80</span><span class="p">)</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_listening</span> <span class="p">}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>service</h3> |
|
<p>Installed services.</p> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="n">service</span><span class="p">(</span><span class="s1">'httpd'</span><span class="p">)</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_running</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_enabled</span> <span class="p">}</span> <span class="c1"># enabled to start when the OS boots.</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>process</h3> |
|
<p>Currently running processes.</p> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="n">process</span><span class="p">(</span><span class="s2">"memchached"</span><span class="p">)</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_running</span> <span class="p">}</span> |
|
|
|
<span class="c1"># parameters from ps, see `man ps(1)`, under "STANDARD FORMAT SPECIFIERS"</span> |
|
<span class="n">its</span><span class="p">(</span><span class="ss">:user</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="n">eq</span> <span class="s1">'root'</span> <span class="p">}</span> |
|
<span class="n">its</span><span class="p">(</span><span class="ss">:args</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="n">match</span> <span class="sr">/-c 32000\b/</span> <span class="p">}</span> |
|
<span class="n">its</span><span class="p">(</span><span class="ss">:nice</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="o">></span> <span class="mi">10</span> <span class="p">}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>command</h3> |
|
<p>Run arbitrary commands and check the results.</p> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="n">command</span><span class="p">(</span><span class="s1">'ls -al /'</span><span class="p">)</span> <span class="k">do</span> |
|
<span class="n">its</span><span class="p">(</span><span class="ss">:stdout</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="n">match</span> <span class="sr">/some pattern/</span> <span class="p">}</span> |
|
<span class="n">its</span><span class="p">(</span><span class="ss">:stderr</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_empty</span> <span class="p">}</span> |
|
<span class="n">its</span><span class="p">(</span><span class="ss">:exit_status</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="n">eq</span> <span class="mi">0</span> <span class="p">}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>host</h3> |
|
<p>Hosts on the network</p> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="n">host</span><span class="p">(</span><span class="s1">'example.org'</span><span class="p">)</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_resolvable</span> <span class="p">}</span> |
|
|
|
<span class="c1"># address</span> |
|
<span class="n">its</span><span class="p">(</span><span class="ss">:ipaddress</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="n">match</span> <span class="sr">/192\.168\.10\.10/</span> <span class="p">}</span> <span class="c1"># could be IPv4 or IPv6</span> |
|
<span class="n">its</span><span class="p">(</span><span class="ss">:ipv4_address</span><span class="p">)</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span> |
|
<span class="n">its</span><span class="p">(</span><span class="ss">:ipv6_address</span><span class="p">)</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span> |
|
|
|
<span class="c1"># reachability</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_reachable</span> <span class="p">}</span> <span class="c1"># ping</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_reachable</span><span class="o">.</span><span class="n">with</span><span class="p">(</span> |
|
<span class="ss">:port</span> <span class="o">=></span> <span class="mi">53</span><span class="p">,</span> <span class="c1"># required parameter</span> |
|
<span class="c1"># Optional params (default values shown)</span> |
|
<span class="ss">:proto</span> <span class="o">=></span> <span class="s1">'tcp'</span><span class="p">,</span> <span class="c1"># or 'udp'</span> |
|
<span class="ss">:timeout</span> <span class="o">=></span> <span class="mi">5</span> <span class="c1"># in seconds.</span> |
|
<span class="p">)}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<h2>Matchers (rspec)</h2> |
|
<p><a href="https://www.relishapp.com/rspec/rspec-expectations/v/3-4/docs/built-in-matchers"> https://www.relishapp.com/rspec/rspec-expectations/v/3-4/docs/built-in-matchers </a></p> |
|
<p>To try these outside of a serverspec test, you'll need to <code>require 'RSpec'</code>, |
|
and replace <code>describe</code> with <code>RSpec.describe</code>.</p> |
|
|
|
<section class='no-break'> |
|
<h3>For strings</h3> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="s1">'foobar'</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">eq</span> <span class="s1">'foobar'</span> <span class="p">}</span> <span class="c1"># match using == operator</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">match</span> <span class="sr">/ooba/</span> <span class="p">}</span> <span class="c1"># match using regex, anywhere in string.</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should_not</span> <span class="n">match</span> <span class="sr">/^ooba$/</span> <span class="p">}</span> <span class="c1"># anchor regex</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should_not</span> <span class="n">be_empty</span> <span class="p">}</span> <span class="c1"># test for empty string: ""</span> |
|
|
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">start_with</span><span class="p">(</span><span class="s1">'fo'</span><span class="p">)</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">end_with</span><span class="p">(</span><span class="s1">'bar'</span><span class="p">)</span> <span class="p">}</span> |
|
|
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_a</span><span class="p">(</span><span class="nb">String</span><span class="p">)</span> <span class="p">}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>For numbers</h3> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="mi">10</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">eq</span> <span class="mi">10</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="o">==</span> <span class="mi">10</span> <span class="p">}</span> <span class="c1"># same as above.</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="o"><</span> <span class="mi">20</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="o"><=</span> <span class="mi">10</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="o">></span> <span class="mi">0</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="o">>=</span> <span class="mi">9</span> <span class="p">}</span> |
|
|
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_within</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">of</span><span class="p">(</span><span class="mi">9</span><span class="p">)</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_within</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">of</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="p">}</span> |
|
|
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_a</span><span class="p">(</span><span class="no">Numeric</span><span class="p">)</span> <span class="p">}</span> <span class="c1"># also consider: Float, Integer</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_an_instance_of</span><span class="p">(</span><span class="no">Fixnum</span><span class="p">)</span> <span class="p">}</span> <span class="c1"># Direct class, no higher</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>For arrays</h3> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="o">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should_not</span> <span class="n">be_empty</span> <span class="p">}</span> <span class="c1"># test for empty list: []</span> |
|
|
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="kp">include</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="p">}</span> <span class="c1"># membership test</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should_not</span> <span class="kp">include</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">}</span> |
|
|
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">all</span><span class="p">(</span> <span class="n">be_an</span><span class="p">(</span><span class="nb">Integer</span><span class="p">)</span> <span class="p">)</span> <span class="p">}</span> <span class="c1"># apply matcher to all elements</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">all</span><span class="p">(</span> <span class="n">be_an</span><span class="p">(</span><span class="nb">Integer</span><span class="p">)</span><span class="o">.</span><span class="n">and</span> <span class="n">be</span> <span class="o"><</span> <span class="mi">10</span> <span class="p">)</span> <span class="p">}</span> <span class="c1"># conjunction</span> |
|
|
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">start_with</span><span class="p">(</span><span class="o">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="o">]</span><span class="p">)</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">end_with</span><span class="p">(</span><span class="o">[</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="o">]</span><span class="p">)</span> <span class="p">}</span> |
|
|
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_an</span><span class="p">(</span><span class="nb">Array</span><span class="p">)</span> <span class="p">}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>For hashes</h3> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span> <span class="p">({</span> <span class="ss">:a</span> <span class="o">=></span> <span class="s1">'A'</span><span class="p">,</span> <span class="ss">:b</span> <span class="o">=></span> <span class="mi">2</span> <span class="p">})</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">have_key</span><span class="p">(</span><span class="ss">:a</span><span class="p">)</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="kp">include</span><span class="p">(</span><span class="ss">:a</span><span class="p">)</span> <span class="p">}</span> <span class="c1"># same as above</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="kp">include</span><span class="p">(</span><span class="ss">:b</span> <span class="o">=></span> <span class="mi">2</span><span class="p">)</span> <span class="p">}</span> <span class="c1"># test for presence of key and the value it maps to.</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should_not</span> <span class="kp">include</span><span class="p">(</span><span class="ss">:b</span> <span class="o">=></span> <span class="mi">3</span><span class="p">)</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should_not</span> <span class="kp">include</span><span class="p">(</span><span class="ss">:c</span> <span class="o">=></span> <span class="mi">2</span><span class="p">)</span> <span class="p">}</span> |
|
|
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_a</span><span class="p">(</span><span class="no">Hash</span><span class="p">)</span> <span class="p">}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>The general purpose <code>satisfy</code> matcher</h3> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span><span class="p">(</span><span class="mi">3</span><span class="o">.</span><span class="mi">14159</span><span class="p">)</span> <span class="k">do</span> |
|
<span class="c1"># Passes as long as the block returns true.</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">satisfy</span> <span class="p">{</span> <span class="o">|</span><span class="n">uut</span><span class="o">|</span> |
|
<span class="n">uut</span><span class="o">.</span><span class="n">kind_of?</span><span class="p">(</span><span class="no">Numeric</span><span class="p">)</span> <span class="ow">and</span> <span class="n">uut</span> <span class="o">></span> <span class="mi">3</span> <span class="ow">and</span> <span class="n">uut</span> <span class="o"><</span> <span class="mi">3</span><span class="o">.</span><span class="mi">2</span> |
|
<span class="p">}}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>Combining expectations (<code>and</code>, <code>or</code>)</h3> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span><span class="p">(</span><span class="s2">"thunder"</span><span class="p">)</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">start_with</span><span class="p">(</span><span class="s2">"thun"</span><span class="p">)</span><span class="o">.</span><span class="n">and</span> <span class="n">end_with</span><span class="p">(</span><span class="s2">"der"</span><span class="p">)</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">start_with</span><span class="p">(</span><span class="s2">"won"</span><span class="p">)</span><span class="o">.</span><span class="n">or</span> <span class="n">start_with</span><span class="p">(</span><span class="s2">"thun"</span><span class="p">)</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="p">(</span><span class="n">start_with</span><span class="p">(</span><span class="s2">"won"</span><span class="p">)</span><span class="o">.</span><span class="n">or</span> <span class="n">start_with</span><span class="p">(</span><span class="s2">"thun"</span><span class="p">))</span><span class="o">.</span><span class="n">and</span> <span class="n">end_with</span><span class="p">(</span><span class="s2">"der"</span><span class="p">)</span> <span class="p">}</span> |
|
|
|
<span class="c1"># with line breaks</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="p">(</span> |
|
<span class="n">start_with</span><span class="p">(</span><span class="s2">"won"</span><span class="p">)</span><span class="o">.</span><span class="n">or</span> <span class="p">\</span> |
|
<span class="n">start_with</span><span class="p">(</span><span class="s2">"thun"</span><span class="p">)</span><span class="o">.</span><span class="n">or</span> <span class="p">\</span> |
|
<span class="n">start_with</span><span class="p">(</span><span class="s2">"pon"</span><span class="p">)</span> |
|
<span class="p">)</span><span class="o">.</span><span class="n">and</span> <span class="p">(</span> |
|
<span class="n">end_with</span><span class="p">(</span><span class="s2">"der"</span><span class="p">)</span> |
|
<span class="p">)</span> |
|
<span class="p">}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
|
|
|
|
<section class='no-break'> |
|
<h3>Defining custom matchers</h3> |
|
<div class="codehilite"><pre><span></span><span class="no">RSpec</span><span class="o">::</span><span class="no">Matchers</span><span class="o">.</span><span class="n">define</span> <span class="ss">:be_multiple_of</span> <span class="k">do</span> <span class="o">|</span><span class="n">expected</span><span class="o">|</span> |
|
<span class="n">match</span> <span class="k">do</span> <span class="o">|</span><span class="n">actual</span><span class="o">|</span> |
|
<span class="n">actual</span> <span class="o">%</span> <span class="n">expected</span> <span class="o">==</span> <span class="mi">0</span> |
|
<span class="k">end</span> |
|
|
|
<span class="c1"># optional, override description</span> |
|
<span class="n">description</span> <span class="k">do</span> |
|
<span class="s2">"be multiple of </span><span class="si">#{</span><span class="n">expected</span><span class="si">}</span><span class="s2">"</span> |
|
<span class="k">end</span> |
|
|
|
<span class="c1"># optionally, override failure message:</span> |
|
<span class="n">failure_message</span> <span class="k">do</span> <span class="o">|</span><span class="n">actual</span><span class="o">|</span> |
|
<span class="s2">"expected that </span><span class="si">#{</span><span class="n">actual</span><span class="si">}</span><span class="s2"> would be a multiple of </span><span class="si">#{</span><span class="n">expected</span><span class="si">}</span><span class="s2">"</span> |
|
<span class="k">end</span> |
|
|
|
<span class="c1"># optionally, override failure message when negated:</span> |
|
<span class="n">failure_message_when_negated</span> <span class="k">do</span> <span class="o">|</span><span class="n">actual</span><span class="o">|</span> |
|
<span class="s2">"expected that </span><span class="si">#{</span><span class="n">actual</span><span class="si">}</span><span class="s2"> would not be a multiple of </span><span class="si">#{</span><span class="n">expected</span><span class="si">}</span><span class="s2">"</span> |
|
<span class="k">end</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
|
|
|
|
<p>Example:</p> |
|
<div class="codehilite"><pre><span></span><span class="n">describe</span><span class="p">(</span><span class="mi">9</span><span class="p">)</span> <span class="k">do</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_multiple_of</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="p">}</span> |
|
|
|
<span class="c1">#Deliberate failures</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_multiple_of</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="p">}</span> |
|
<span class="n">it</span> <span class="p">{</span> <span class="n">should_not</span> <span class="n">be_multiple_of</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="p">}</span> |
|
<span class="k">end</span> |
|
</pre></div> |
|
</section> |
|
</div> |
|
|
|
</body> |
|
</html> |