From d911036768e0c48d872759557c92b9479a202bfa Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Fri, 8 Feb 2019 08:53:23 -0800 Subject: [PATCH 01/13] Better handling of malformed job arguments in payload, fixes #4095 --- Changes.md | 5 +++++ lib/sidekiq/version.rb | 2 +- lib/sidekiq/web/helpers.rb | 13 ++++++++++--- test/test_web_helpers.rb | 12 ++++++++++++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Changes.md b/Changes.md index 488336b43..f33671347 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,11 @@ [Sidekiq Changes](https://github.com/mperham/sidekiq/blob/master/Changes.md) | [Sidekiq Pro Changes](https://github.com/mperham/sidekiq/blob/master/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/mperham/sidekiq/blob/master/Ent-Changes.md) +HEAD +--------- + +- Better handling of malformed job arguments in payload [#4095] + 5.2.5 --------- diff --git a/lib/sidekiq/version.rb b/lib/sidekiq/version.rb index 3928a10a4..f5c23ffd3 100644 --- a/lib/sidekiq/version.rb +++ b/lib/sidekiq/version.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true module Sidekiq - VERSION = "5.2.5" + VERSION = "5.2.6" end diff --git a/lib/sidekiq/web/helpers.rb b/lib/sidekiq/web/helpers.rb index d9ca95b9c..35b56679f 100644 --- a/lib/sidekiq/web/helpers.rb +++ b/lib/sidekiq/web/helpers.rb @@ -207,9 +207,16 @@ def truncate(text, truncate_after_chars = 2000) end def display_args(args, truncate_after_chars = 2000) - args.map do |arg| - h(truncate(to_display(arg), truncate_after_chars)) - end.join(", ") + return "Invalid job payload, args is nil" if args == nil + return "Invalid job payload, args must be an Array, not #{args.class.name}" if !args.is_a?(Array) + + begin + args.map do |arg| + h(truncate(to_display(arg), truncate_after_chars)) + end.join(", ") + rescue + "Illegal job arguments: #{h args.inspect}" + end end def csrf_tag diff --git a/test/test_web_helpers.rb b/test/test_web_helpers.rb index 924ddc1fd..a95d6b2fb 100644 --- a/test/test_web_helpers.rb +++ b/test/test_web_helpers.rb @@ -95,4 +95,16 @@ def test_available_locales ) assert_equal expected, obj.available_locales.sort end + + def test_display_illegal_args + o = Helpers.new + s = o.display_args([1,2,3]) + assert_equal "1, 2, 3", s + s = o.display_args(["", 12]) + assert_equal ""<html>", 12", s + s = o.display_args("") + assert_equal "Invalid job payload, args must be an Array, not String", s + s = o.display_args(nil) + assert_equal "Invalid job payload, args is nil", s + end end From 731b5d69e400a6a0013f257414aaf3b50292d686 Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Fri, 8 Feb 2019 08:53:50 -0800 Subject: [PATCH 02/13] cleanup --- test/dummy/config/application.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/dummy/config/application.rb b/test/dummy/config/application.rb index c7a9d5d65..a0e634cb7 100644 --- a/test/dummy/config/application.rb +++ b/test/dummy/config/application.rb @@ -7,5 +7,6 @@ class Application < Rails::Application config.root = File.expand_path("../..", __FILE__) config.eager_load = false config.logger = Logger.new('/dev/null') + config.active_record.sqlite3.represent_boolean_as_integer = true end end From e8ad1a100479fd65880951d50c7a725e0c746127 Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Fri, 8 Feb 2019 09:24:23 -0800 Subject: [PATCH 03/13] releases --- Ent-Changes.md | 2 +- Pro-Changes.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Ent-Changes.md b/Ent-Changes.md index ef0211183..18aeecea2 100644 --- a/Ent-Changes.md +++ b/Ent-Changes.md @@ -4,7 +4,7 @@ Please see [http://sidekiq.org/](http://sidekiq.org/) for more details and how to buy. -HEAD +1.8.0 ------------- - Require Sidekiq Pro 4.0 and Sidekiq 5.2. diff --git a/Pro-Changes.md b/Pro-Changes.md index 158041d10..0c50ae70b 100644 --- a/Pro-Changes.md +++ b/Pro-Changes.md @@ -4,7 +4,7 @@ Please see [http://sidekiq.org/](http://sidekiq.org/) for more details and how to buy. -HEAD +4.0.5 --------- - Increase super\_fetch retriever thread count from 1 to 2 to make it From 5dce7be9f6a67e5c5031a990d51d7b295b317865 Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Thu, 14 Feb 2019 06:42:06 -0800 Subject: [PATCH 04/13] ES locale --- Ent-Changes.md | 7 ++++++- Pro-Changes.md | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Ent-Changes.md b/Ent-Changes.md index 18aeecea2..d24d366bd 100644 --- a/Ent-Changes.md +++ b/Ent-Changes.md @@ -4,6 +4,11 @@ Please see [http://sidekiq.org/](http://sidekiq.org/) for more details and how to buy. +HEAD +------------- + +- Add ES translations, see issues [#3949](https://github.com/mperham/sidekiq/issues/3949) and [#3951](https://github.com/mperham/sidekiq/issues/3951) to add your own language. + 1.8.0 ------------- @@ -14,7 +19,7 @@ Please see [http://sidekiq.org/](http://sidekiq.org/) for more details and how t 1.7.2 ------------- -- Add PT and JA translations, see issues [#3949](https://github.com/mperham/sidekiq/issues/3949) and [#3951](https://github.com/mperham/sidekiq/issues/3951) to add your own language. +- Add PT and JA translations - Fix elapsed time calculations to use monotonic clock [#4000, sj26] - Fix edge case where flapping leadership would cause old periodic jobs to be fired once [#3974] diff --git a/Pro-Changes.md b/Pro-Changes.md index 0c50ae70b..a6010d093 100644 --- a/Pro-Changes.md +++ b/Pro-Changes.md @@ -4,13 +4,18 @@ Please see [http://sidekiq.org/](http://sidekiq.org/) for more details and how to buy. +HEAD +--------- + +- Add ES translations, see issues [#3949](https://github.com/mperham/sidekiq/issues/3949) and [#3951](https://github.com/mperham/sidekiq/issues/3951) to add your own language. + 4.0.5 --------- - Increase super\_fetch retriever thread count from 1 to 2 to make it less sensitive to Redis latency. - Better handling of invalid job JSON by reliable scheduler [#4053] -- Added ZH, PT, JA and RU translations, see issues [#3949](https://github.com/mperham/sidekiq/issues/3949) and [#3951](https://github.com/mperham/sidekiq/issues/3951) to add your own language. +- Added ZH, PT, JA and RU translations. 4.0.4 --------- From 771671e45c1620f3416484695d182272d84f4a1d Mon Sep 17 00:00:00 2001 From: Edward Rudd Date: Fri, 22 Feb 2019 12:12:04 -0500 Subject: [PATCH 05/13] add back the dropdown css (#4103) --- Changes.md | 1 + web/assets/stylesheets/bootstrap.css | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes.md b/Changes.md index f33671347..654825f7d 100644 --- a/Changes.md +++ b/Changes.md @@ -6,6 +6,7 @@ HEAD --------- - Better handling of malformed job arguments in payload [#4095] +- add back in bootstap's dropdown css component [#4099, urkle] 5.2.5 --------- diff --git a/web/assets/stylesheets/bootstrap.css b/web/assets/stylesheets/bootstrap.css index cfb569d3c..6b8c25626 100644 --- a/web/assets/stylesheets/bootstrap.css +++ b/web/assets/stylesheets/bootstrap.css @@ -2,4 +2,4 @@ * Bootstrap v3.3.7 (http://getbootstrap.com) - Glyphicons * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:hover,a:focus{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{background-color:#fcf8e3;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover,a.text-primary:focus{color:#286090}.text-success{color:#3c763d}a.text-success:hover,a.text-success:focus{color:#2b542c}.text-info{color:#31708f}a.text-info:hover,a.text-info:focus{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover,a.text-warning:focus{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover,a.text-danger:focus{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover,a.bg-primary:focus{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover,a.bg-success:focus{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover,a.bg-info:focus{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover,a.bg-warning:focus{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover,a.bg-danger:focus{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:34px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:30px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:focus,.btn-default.focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary:focus,.btn-primary.focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:focus,.btn-success.focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#fff;background-color:#398439;border-color:#255625}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:focus,.btn-info.focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:focus,.btn-warning.focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:focus,.btn-danger.focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#337ab7;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:8px;margin-bottom:8px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#080808;color:#fff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857143;text-decoration:none;color:#337ab7;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#fff;background-color:#337ab7;border-color:#337ab7;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;color:#fff;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px;padding-left:15px;padding-right:15px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{text-decoration:none;color:#555;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eee;color:#777;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after{content:" ";display:table}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}} + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */.label,sub,sup{vertical-align:baseline}hr,img{border:0}body,figure{margin:0}.btn-group>.btn-group,.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.dropdown-menu{float:left}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.pre-scrollable{max-height:340px}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0}mark{background:#ff0;color:#000}sub,sup{font-size:75%;line-height:0;position:relative}sup{top:-.5em}sub{bottom:-.25em}img{vertical-align:middle}svg:not(:root){overflow:hidden}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{blockquote,img,pre,tr{page-break-inside:avoid}*,:after,:before{background:0 0!important;color:#000!important;-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999}thead{display:table-header-group}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}.btn,.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-warning.active,.btn-warning:active,.btn.active,.btn:active,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover,.form-control,.navbar-toggle,.open>.dropdown-toggle.btn-danger,.open>.dropdown-toggle.btn-default,.open>.dropdown-toggle.btn-info,.open>.dropdown-toggle.btn-primary,.open>.dropdown-toggle.btn-warning{background-image:none}.img-thumbnail,body{background-color:#fff}*,:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:transparent}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}dt,kbd kbd,label{font-weight:700}address,blockquote .small,blockquote footer,blockquote small,dd,dt,pre{line-height:1.42857143}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{background-color:#fcf8e3;padding:.2em}.list-inline,.list-unstyled{padding-left:0;list-style:none}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}pre code,table{background-color:transparent}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}dl,ol,ul{margin-top:0}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child,ol ol,ol ul,ul ol,ul ul{margin-bottom:0}address,dl{margin-bottom:20px}ol,ul{margin-bottom:10px}.list-inline{margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}.container{width:750px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;color:#777}legend,pre{display:block;color:#333}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}code,kbd{padding:2px 4px;font-size:90%}caption,th{text-align:left}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{font-style:normal}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;-webkit-box-shadow:none;box-shadow:none}pre{padding:9.5px;margin:0 0 10px;font-size:13px;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}.container,.container-fluid{margin-right:auto;margin-left:auto}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;border-radius:0}.container,.container-fluid{padding-left:15px;padding-right:15px}.pre-scrollable{overflow-y:scroll}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.row{margin-left:-15px;margin-right:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}caption{padding-top:8px;padding-bottom:8px;color:#777}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered,.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}table col[class*=col-]{position:static;float:none;display:table-column}table td[class*=col-],table th[class*=col-]{position:static;float:none;display:table-cell}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{overflow-x:auto;min-height:.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset,legend{padding:0;border:0}fieldset{margin:0;min-width:0}legend{width:100%;margin-bottom:20px;font-size:21px;line-height:inherit;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}.form-control,output{font-size:14px;line-height:1.42857143;color:#555;display:block}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}output{padding-top:7px}.form-control{width:100%;height:34px;padding:6px 12px;background-color:#fff;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .form-control-feedback,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-left:-20px;margin-top:4px\9}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}.checkbox-inline.disabled,.checkbox.disabled label,.radio-inline.disabled,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio label,fieldset[disabled] .radio-inline,fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.form-group-sm .form-control,.input-sm{padding:5px 10px;border-radius:3px;font-size:12px}.input-sm{height:30px;line-height:1.5}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;line-height:1.5}.form-group-lg .form-control,.input-lg{border-radius:6px;padding:10px 16px;font-size:18px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;line-height:1.3333333}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;line-height:1.3333333}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .form-control-feedback,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .form-control-feedback,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-control-static,.form-inline .form-group{display:inline-block}.form-inline .control-label,.form-inline .form-group{margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle,.btn.active,.btn:active,.dropdown-toggle:focus,.navbar-toggle:focus,.open>a{outline:0}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary.active,.btn-primary:active,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success.active,.btn-success:active,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info.active,.btn-info:active,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger.active,.btn-danger:active,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#337ab7;font-weight:400;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu-right,.dropdown-menu.pull-right{right:0;left:auto}.dropdown-header,.dropdown-menu>li>a{display:block;padding:3px 20px;line-height:1.42857143;white-space:nowrap}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle,.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child,.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child),.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn,.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{clear:both;font-weight:400;color:#333}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.dropdown-menu-left{right:auto;left:0}.dropdown-header{font-size:12px;color:#777}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.nav-justified>.dropdown .dropdown-menu,.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn .caret,.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn-lg .caret{border-width:5px 5px 0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child:not(:first-child){border-radius:0 0 4px 4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.nav>li,.nav>li>a{display:block;position:relative}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li>a{padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px;margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0;border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-justified>li,.nav-stacked>li{float:none}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.navbar{border-radius:4px}.navbar-header{float:left}.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-left:0;padding-right:0}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}.navbar-static-top{border-radius:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:4px}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}.progress-bar-striped,.progress-striped .progress-bar,.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}@media (min-width:768px){.navbar-toggle{display:none}.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin:8px -15px}@media (min-width:768px){.navbar-form .form-control-static,.navbar-form .form-group{display:inline-block}.navbar-form .control-label,.navbar-form .form-group{margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.breadcrumb>li,.pagination{display:inline-block}.btn .badge,.btn .label{top:-1px;position:relative}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-radius:4px 4px 0 0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-nav>li>a,.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{background-color:#e7e7e7;color:#555}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>li>a,.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{background-color:#080808;color:#fff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#777}.pagination{padding-left:0;margin:20px 0;border-radius:4px}.pager li,.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857143;text-decoration:none;color:#337ab7;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;background-color:#337ab7;border-color:#337ab7;cursor:default}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.badge,.label{font-weight:700;line-height:1;white-space:nowrap;text-align:center}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;background-color:#fff;cursor:not-allowed}a.badge:focus,a.badge:hover,a.label:focus,a.label:hover{color:#fff;cursor:pointer;text-decoration:none}.label{display:inline;padding:.2em .6em .3em;font-size:75%;color:#fff;border-radius:.25em}.label:empty{display:none}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;color:#fff;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.media-object,.thumbnail{display:block}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.jumbotron,.jumbotron .h1,.jumbotron h1{color:inherit}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.center-block,.thumbnail a>img,.thumbnail>img{margin-left:auto;margin-right:auto}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;background-color:#eee}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.alert,.thumbnail{margin-bottom:20px}.alert .alert-link,.close{font-weight:700}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px;padding-left:15px;padding-right:15px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-striped .progress-bar-info,.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{text-decoration:none;color:#555;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{background-color:#eee;color:#777;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.panel-heading>.dropdown .dropdown-toggle,.panel-title,.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-title,.panel>.list-group,.panel>.panel-collapse>.list-group,.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-title{margin-top:0;font-size:16px}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel-group .panel-heading,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-responsive:last-child>.table:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel>.table-responsive:first-child>.table:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.list-group+.panel-footer,.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-left:15px;padding-right:15px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{content:" ";display:table}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.hidden,.visible-lg,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.affix{position:fixed}@-ms-viewport{width:device-width}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}.visible-xs-block{display:block!important}.visible-xs-inline{display:inline!important}.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}.visible-sm-block{display:block!important}.visible-sm-inline{display:inline!important}.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}.visible-md-block{display:block!important}.visible-md-inline{display:inline!important}.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}.visible-lg-block{display:block!important}.visible-lg-inline{display:inline!important}.visible-lg-inline-block{display:inline-block!important}.hidden-lg{display:none!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}.hidden-print{display:none!important}} \ No newline at end of file From ac1d36bd7978e47627b319cd0b167eb380a9ca6b Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Thu, 28 Feb 2019 09:02:09 -0800 Subject: [PATCH 06/13] ent change --- Ent-Changes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Ent-Changes.md b/Ent-Changes.md index d24d366bd..d5ae439a1 100644 --- a/Ent-Changes.md +++ b/Ent-Changes.md @@ -7,6 +7,7 @@ Please see [http://sidekiq.org/](http://sidekiq.org/) for more details and how t HEAD ------------- +- Fix excessive lock reclaims with concurrent limiter [#4105] - Add ES translations, see issues [#3949](https://github.com/mperham/sidekiq/issues/3949) and [#3951](https://github.com/mperham/sidekiq/issues/3951) to add your own language. 1.8.0 From 870516f4726634a58a0f9b1ca45b6b31194e2081 Mon Sep 17 00:00:00 2001 From: Josh Loewen Date: Thu, 28 Feb 2019 10:06:41 -0800 Subject: [PATCH 07/13] Format latency (#4111) * Format latency time * format latency when 0 * only show human-readable time if 60s or more * memoize to reduce repetitive Redis calls * removing use of seconds.ago due to no ActiveSupport in Sidekiq --- web/views/queues.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/views/queues.erb b/web/views/queues.erb index c6e349588..9336b971a 100644 --- a/web/views/queues.erb +++ b/web/views/queues.erb @@ -17,7 +17,7 @@ <% end %> <%= number_with_delimiter(queue.size) %> - <%= number_with_delimiter(queue.latency.round(2)) %> + <% queue_latency = queue.latency %><%= number_with_delimiter(queue_latency.round(2)) %><%= (queue_latency < 60) ? '' : " (#{relative_time(Time.at(Time.now.to_f - queue_latency))})" %>
<%= csrf_tag %> From 85a1be368486e22e17ee8a30bce8b4a8f7b9dca2 Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Thu, 28 Feb 2019 12:43:50 -0800 Subject: [PATCH 08/13] Switch all tests to use Minitest::Spec for consistency --- test/test_actors.rb | 190 +++--- test/test_api.rb | 934 ++++++++++++++------------- test/test_cli.rb | 2 +- test/test_client.rb | 2 +- test/test_dead_set.rb | 68 +- test/test_exception_handler.rb | 2 +- test/test_extensions.rb | 175 +++--- test/test_fetch.rb | 68 +- test/test_launcher.rb | 129 ++-- test/test_logging.rb | 42 +- test/test_manager.rb | 71 +-- test/test_middleware.rb | 180 +++--- test/test_processor.rb | 542 ++++++++-------- test/test_rails.rb | 25 +- test/test_redis_connection.rb | 4 +- test/test_retry.rb | 2 +- test/test_retry_exhausted.rb | 252 ++++---- test/test_scheduled.rb | 8 +- test/test_scheduling.rb | 18 +- test/test_sidekiq.rb | 2 +- test/test_sidekiqctl.rb | 4 +- test/test_testing.rb | 108 ++-- test/test_testing_fake.rb | 400 ++++++------ test/test_testing_inline.rb | 122 ++-- test/test_web.rb | 1079 ++++++++++++++++---------------- test/test_web_helpers.rb | 2 +- 26 files changed, 2194 insertions(+), 2237 deletions(-) diff --git a/test/test_actors.rb b/test/test_actors.rb index ca46209bc..739397a94 100644 --- a/test/test_actors.rb +++ b/test/test_actors.rb @@ -5,7 +5,7 @@ require 'sidekiq/scheduled' require 'sidekiq/processor' -class TestActors < Minitest::Test +describe 'Actors' do class JoeWorker include Sidekiq::Worker def perform(slp) @@ -15,124 +15,122 @@ def perform(slp) end end - describe 'threads' do - before do - Sidekiq.redis {|c| c.flushdb} + before do + Sidekiq.redis {|c| c.flushdb} + end + + describe 'scheduler' do + it 'can start and stop' do + f = Sidekiq::Scheduled::Poller.new + f.start + f.terminate end - describe 'scheduler' do - it 'can start and stop' do - f = Sidekiq::Scheduled::Poller.new - f.start - f.terminate - end + it 'can schedule' do + ss = Sidekiq::ScheduledSet.new + q = Sidekiq::Queue.new - it 'can schedule' do - ss = Sidekiq::ScheduledSet.new - q = Sidekiq::Queue.new + JoeWorker.perform_in(0.01, 0) - JoeWorker.perform_in(0.01, 0) + assert_equal 0, q.size + assert_equal 1, ss.size - assert_equal 0, q.size - assert_equal 1, ss.size + sleep 0.015 + s = Sidekiq::Scheduled::Poller.new + s.enqueue + assert_equal 1, q.size + assert_equal 0, ss.size + s.terminate + end + end - sleep 0.015 - s = Sidekiq::Scheduled::Poller.new - s.enqueue - assert_equal 1, q.size - assert_equal 0, ss.size - s.terminate - end + describe 'processor' do + before do + $count = 0 end - describe 'processor' do - before do - $count = 0 - end + it 'can start and stop' do + f = Sidekiq::Processor.new(Mgr.new) + f.terminate + end - it 'can start and stop' do - f = Sidekiq::Processor.new(Mgr.new) - f.terminate + class Mgr + attr_reader :latest_error + attr_reader :mutex + attr_reader :cond + def initialize + @mutex = ::Mutex.new + @cond = ::ConditionVariable.new end - - class Mgr - attr_reader :latest_error - attr_reader :mutex - attr_reader :cond - def initialize - @mutex = ::Mutex.new - @cond = ::ConditionVariable.new - end - def processor_died(inst, err) - @latest_error = err - @mutex.synchronize do - @cond.signal - end + def processor_died(inst, err) + @latest_error = err + @mutex.synchronize do + @cond.signal end - def processor_stopped(inst) - @mutex.synchronize do - @cond.signal - end - end - def options - { :concurrency => 3, :queues => ['default'] } + end + def processor_stopped(inst) + @mutex.synchronize do + @cond.signal end end + def options + { :concurrency => 3, :queues => ['default'] } + end + end - it 'can process' do - mgr = Mgr.new + it 'can process' do + mgr = Mgr.new - p = Sidekiq::Processor.new(mgr) - JoeWorker.perform_async(0) + p = Sidekiq::Processor.new(mgr) + JoeWorker.perform_async(0) - a = $count - p.process_one - b = $count - assert_equal a + 1, b - end + a = $count + p.process_one + b = $count + assert_equal a + 1, b + end - it 'deals with errors' do - mgr = Mgr.new + it 'deals with errors' do + mgr = Mgr.new - p = Sidekiq::Processor.new(mgr) - JoeWorker.perform_async("boom") - q = Sidekiq::Queue.new - assert_equal 1, q.size + p = Sidekiq::Processor.new(mgr) + JoeWorker.perform_async("boom") + q = Sidekiq::Queue.new + assert_equal 1, q.size - a = $count - mgr.mutex.synchronize do - p.start - mgr.cond.wait(mgr.mutex) - end - b = $count - assert_equal a, b - - sleep 0.001 - assert_equal false, p.thread.status - p.terminate(true) - refute_nil mgr.latest_error - assert_equal RuntimeError, mgr.latest_error.class + a = $count + mgr.mutex.synchronize do + p.start + mgr.cond.wait(mgr.mutex) end + b = $count + assert_equal a, b + + sleep 0.001 + assert_equal false, p.thread.status + p.terminate(true) + refute_nil mgr.latest_error + assert_equal RuntimeError, mgr.latest_error.class + end - it 'gracefully kills' do - mgr = Mgr.new + it 'gracefully kills' do + mgr = Mgr.new - p = Sidekiq::Processor.new(mgr) - JoeWorker.perform_async(1) - q = Sidekiq::Queue.new - assert_equal 1, q.size + p = Sidekiq::Processor.new(mgr) + JoeWorker.perform_async(1) + q = Sidekiq::Queue.new + assert_equal 1, q.size - a = $count - p.start - sleep(0.05) - p.terminate - p.kill(true) - - b = $count - assert_equal a, b - assert_equal false, p.thread.status - refute mgr.latest_error, mgr.latest_error.to_s - end + a = $count + p.start + sleep(0.05) + p.terminate + p.kill(true) + + b = $count + assert_equal a, b + assert_equal false, p.thread.status + refute mgr.latest_error, mgr.latest_error.to_s end end end diff --git a/test/test_api.rb b/test/test_api.rb index 629b05ed5..f7e4cd80e 100644 --- a/test/test_api.rb +++ b/test/test_api.rb @@ -4,573 +4,571 @@ require 'active_job' require 'action_mailer' -class TestApi < Minitest::Test - describe 'api' do - before do - Sidekiq.redis {|c| c.flushdb } +describe 'API' do + before do + Sidekiq.redis {|c| c.flushdb } + end + + describe 'RedisScanner' do + it 'returns identical to smembers' do + test_obj = Object.new + test_obj.extend(Sidekiq::RedisScanner) + 50.times do |i| + Sidekiq.redis { |conn| conn.sadd('processes', "test-process-#{i}") } + end + sscan = Sidekiq.redis { |c| test_obj.sscan(c, 'processes') }.sort! + smembers = Sidekiq.redis { |c| c.smembers('processes') }.sort! + assert_equal sscan.size, 50 + assert_equal sscan, smembers end + end - describe 'RedisScanner' do - it 'returns identical to smembers' do - test_obj = Object.new - test_obj.extend(Sidekiq::RedisScanner) - 50.times do |i| - Sidekiq.redis { |conn| conn.sadd('processes', "test-process-#{i}") } - end - sscan = Sidekiq.redis { |c| test_obj.sscan(c, 'processes') }.sort! - smembers = Sidekiq.redis { |c| c.smembers('processes') }.sort! - assert_equal sscan.size, 50 - assert_equal sscan, smembers + describe "stats" do + it "is initially zero" do + s = Sidekiq::Stats.new + assert_equal 0, s.processed + assert_equal 0, s.failed + assert_equal 0, s.enqueued + assert_equal 0, s.default_queue_latency + end + + describe "processed" do + it "returns number of processed jobs" do + Sidekiq.redis { |conn| conn.set("stat:processed", 5) } + s = Sidekiq::Stats.new + assert_equal 5, s.processed + end + end + + describe "failed" do + it "returns number of failed jobs" do + Sidekiq.redis { |conn| conn.set("stat:failed", 5) } + s = Sidekiq::Stats.new + assert_equal 5, s.failed end end - describe "stats" do - it "is initially zero" do + describe "reset" do + before do + Sidekiq.redis do |conn| + conn.set('stat:processed', 5) + conn.set('stat:failed', 10) + end + end + + it 'will reset all stats by default' do + Sidekiq::Stats.new.reset s = Sidekiq::Stats.new + assert_equal 0, s.failed assert_equal 0, s.processed + end + + it 'can reset individual stats' do + Sidekiq::Stats.new.reset('failed') + s = Sidekiq::Stats.new assert_equal 0, s.failed - assert_equal 0, s.enqueued - assert_equal 0, s.default_queue_latency + assert_equal 5, s.processed end - describe "processed" do - it "returns number of processed jobs" do - Sidekiq.redis { |conn| conn.set("stat:processed", 5) } - s = Sidekiq::Stats.new - assert_equal 5, s.processed - end + it 'can accept anything that responds to #to_s' do + Sidekiq::Stats.new.reset(:failed) + s = Sidekiq::Stats.new + assert_equal 0, s.failed + assert_equal 5, s.processed end - describe "failed" do - it "returns number of failed jobs" do - Sidekiq.redis { |conn| conn.set("stat:failed", 5) } - s = Sidekiq::Stats.new - assert_equal 5, s.failed - end + it 'ignores anything other than "failed" or "processed"' do + Sidekiq::Stats.new.reset((1..10).to_a, ['failed']) + s = Sidekiq::Stats.new + assert_equal 0, s.failed + assert_equal 5, s.processed end + end - describe "reset" do - before do - Sidekiq.redis do |conn| - conn.set('stat:processed', 5) - conn.set('stat:failed', 10) - end - end + describe "queues" do + it "is initially empty" do + s = Sidekiq::Stats::Queues.new + assert_equal 0, s.lengths.size + end - it 'will reset all stats by default' do - Sidekiq::Stats.new.reset - s = Sidekiq::Stats.new - assert_equal 0, s.failed - assert_equal 0, s.processed - end + it "returns a hash of queue and size in order" do + Sidekiq.redis do |conn| + conn.rpush 'queue:foo', '{}' + conn.sadd 'queues', 'foo' - it 'can reset individual stats' do - Sidekiq::Stats.new.reset('failed') - s = Sidekiq::Stats.new - assert_equal 0, s.failed - assert_equal 5, s.processed + 3.times { conn.rpush 'queue:bar', '{}' } + conn.sadd 'queues', 'bar' end - it 'can accept anything that responds to #to_s' do - Sidekiq::Stats.new.reset(:failed) - s = Sidekiq::Stats.new - assert_equal 0, s.failed - assert_equal 5, s.processed - end + s = Sidekiq::Stats::Queues.new + assert_equal ({ "foo" => 1, "bar" => 3 }), s.lengths + assert_equal "bar", s.lengths.first.first + + assert_equal Sidekiq::Stats.new.queues, Sidekiq::Stats::Queues.new.lengths + end + end - it 'ignores anything other than "failed" or "processed"' do - Sidekiq::Stats.new.reset((1..10).to_a, ['failed']) - s = Sidekiq::Stats.new - assert_equal 0, s.failed - assert_equal 5, s.processed + describe "enqueued" do + it 'handles latency for good jobs' do + Sidekiq.redis do |conn| + conn.rpush 'queue:default', "{\"enqueued_at\": #{Time.now.to_f}}" + conn.sadd 'queues', 'default' end + s = Sidekiq::Stats.new + assert s.default_queue_latency > 0 + q = Sidekiq::Queue.new + assert q.latency > 0 end - describe "queues" do - it "is initially empty" do - s = Sidekiq::Stats::Queues.new - assert_equal 0, s.lengths.size + it 'handles latency for incomplete jobs' do + Sidekiq.redis do |conn| + conn.rpush 'queue:default', '{}' + conn.sadd 'queues', 'default' end + s = Sidekiq::Stats.new + assert_equal 0, s.default_queue_latency + q = Sidekiq::Queue.new + assert_equal 0, q.latency + end - it "returns a hash of queue and size in order" do - Sidekiq.redis do |conn| - conn.rpush 'queue:foo', '{}' - conn.sadd 'queues', 'foo' + it "returns total enqueued jobs" do + Sidekiq.redis do |conn| + conn.rpush 'queue:foo', '{}' + conn.sadd 'queues', 'foo' - 3.times { conn.rpush 'queue:bar', '{}' } - conn.sadd 'queues', 'bar' - end + 3.times { conn.rpush 'queue:bar', '{}' } + conn.sadd 'queues', 'bar' + end - s = Sidekiq::Stats::Queues.new - assert_equal ({ "foo" => 1, "bar" => 3 }), s.lengths - assert_equal "bar", s.lengths.first.first + s = Sidekiq::Stats.new + assert_equal 4, s.enqueued + end + end - assert_equal Sidekiq::Stats.new.queues, Sidekiq::Stats::Queues.new.lengths - end + describe "over time" do + before do + require 'active_support/core_ext/time/conversions' + @before = Time::DATE_FORMATS[:default] + Time::DATE_FORMATS[:default] = "%d/%m/%Y %H:%M:%S" end - describe "enqueued" do - it 'handles latency for good jobs' do - Sidekiq.redis do |conn| - conn.rpush 'queue:default', "{\"enqueued_at\": #{Time.now.to_f}}" - conn.sadd 'queues', 'default' - end - s = Sidekiq::Stats.new - assert s.default_queue_latency > 0 - q = Sidekiq::Queue.new - assert q.latency > 0 - end + after do + Time::DATE_FORMATS[:default] = @before + end - it 'handles latency for incomplete jobs' do - Sidekiq.redis do |conn| - conn.rpush 'queue:default', '{}' - conn.sadd 'queues', 'default' + describe "processed" do + it 'retrieves hash of dates' do + Sidekiq.redis do |c| + c.incrby("stat:processed:2012-12-24", 4) + c.incrby("stat:processed:2012-12-25", 1) + c.incrby("stat:processed:2012-12-26", 6) + c.incrby("stat:processed:2012-12-27", 2) end - s = Sidekiq::Stats.new - assert_equal 0, s.default_queue_latency - q = Sidekiq::Queue.new - assert_equal 0, q.latency - end + Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do + s = Sidekiq::Stats::History.new(2) + assert_equal({ "2012-12-26" => 6, "2012-12-25" => 1 }, s.processed) - it "returns total enqueued jobs" do - Sidekiq.redis do |conn| - conn.rpush 'queue:foo', '{}' - conn.sadd 'queues', 'foo' + s = Sidekiq::Stats::History.new(3) + assert_equal({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }, s.processed) - 3.times { conn.rpush 'queue:bar', '{}' } - conn.sadd 'queues', 'bar' + s = Sidekiq::Stats::History.new(2, Date.parse("2012-12-25")) + assert_equal({ "2012-12-25" => 1, "2012-12-24" => 4 }, s.processed) end - - s = Sidekiq::Stats.new - assert_equal 4, s.enqueued end end - describe "over time" do - before do - require 'active_support/core_ext/time/conversions' - @before = Time::DATE_FORMATS[:default] - Time::DATE_FORMATS[:default] = "%d/%m/%Y %H:%M:%S" - end - - after do - Time::DATE_FORMATS[:default] = @before - end - - describe "processed" do - it 'retrieves hash of dates' do - Sidekiq.redis do |c| - c.incrby("stat:processed:2012-12-24", 4) - c.incrby("stat:processed:2012-12-25", 1) - c.incrby("stat:processed:2012-12-26", 6) - c.incrby("stat:processed:2012-12-27", 2) - end - Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do - s = Sidekiq::Stats::History.new(2) - assert_equal({ "2012-12-26" => 6, "2012-12-25" => 1 }, s.processed) - - s = Sidekiq::Stats::History.new(3) - assert_equal({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }, s.processed) - - s = Sidekiq::Stats::History.new(2, Date.parse("2012-12-25")) - assert_equal({ "2012-12-25" => 1, "2012-12-24" => 4 }, s.processed) - end + describe "failed" do + it 'retrieves hash of dates' do + Sidekiq.redis do |c| + c.incrby("stat:failed:2012-12-24", 4) + c.incrby("stat:failed:2012-12-25", 1) + c.incrby("stat:failed:2012-12-26", 6) + c.incrby("stat:failed:2012-12-27", 2) end - end + Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do + s = Sidekiq::Stats::History.new(2) + assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1 }), s.failed + + s = Sidekiq::Stats::History.new(3) + assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed - describe "failed" do - it 'retrieves hash of dates' do - Sidekiq.redis do |c| - c.incrby("stat:failed:2012-12-24", 4) - c.incrby("stat:failed:2012-12-25", 1) - c.incrby("stat:failed:2012-12-26", 6) - c.incrby("stat:failed:2012-12-27", 2) - end - Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do - s = Sidekiq::Stats::History.new(2) - assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1 }), s.failed - - s = Sidekiq::Stats::History.new(3) - assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed - - s = Sidekiq::Stats::History.new(2, Date.parse("2012-12-25")) - assert_equal ({ "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed - end + s = Sidekiq::Stats::History.new(2, Date.parse("2012-12-25")) + assert_equal ({ "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed end end end end + end - describe 'with an empty database' do - it 'shows queue as empty' do - q = Sidekiq::Queue.new - assert_equal 0, q.size - assert_equal 0, q.latency - end + describe 'with an empty database' do + it 'shows queue as empty' do + q = Sidekiq::Queue.new + assert_equal 0, q.size + assert_equal 0, q.latency + end - before do - ActiveJob::Base.queue_adapter = :sidekiq - ActiveJob::Base.logger = nil - end + before do + ActiveJob::Base.queue_adapter = :sidekiq + ActiveJob::Base.logger = nil + end - class ApiMailer < ActionMailer::Base - def test_email(*) - end + class ApiMailer < ActionMailer::Base + def test_email(*) end + end - class ApiJob < ActiveJob::Base - def perform(*) - end + class ApiJob < ActiveJob::Base + def perform(*) end + end - class ApiWorker - include Sidekiq::Worker - end + class ApiWorker + include Sidekiq::Worker + end - it 'can enumerate jobs' do - q = Sidekiq::Queue.new - Time.stub(:now, Time.new(2012, 12, 26)) do - ApiWorker.perform_async(1, 'mike') - assert_equal ['TestApi::ApiWorker'], q.map(&:klass) - - job = q.first - assert_equal 24, job.jid.size - assert_equal [1, 'mike'], job.args - assert_equal Time.new(2012, 12, 26), job.enqueued_at - end - assert q.latency > 10_000_000 + it 'can enumerate jobs' do + q = Sidekiq::Queue.new + Time.stub(:now, Time.new(2012, 12, 26)) do + ApiWorker.perform_async(1, 'mike') + assert_equal [ApiWorker.name], q.map(&:klass) - q = Sidekiq::Queue.new('other') - assert_equal 0, q.size + job = q.first + assert_equal 24, job.jid.size + assert_equal [1, 'mike'], job.args + assert_equal Time.new(2012, 12, 26), job.enqueued_at end + assert q.latency > 10_000_000 - it 'enumerates jobs in descending score order' do - # We need to enqueue more than 50 items, which is the page size when retrieving - # from Redis to ensure everything is sorted: the pages and the items withing them. - 51.times { ApiWorker.perform_in(100, 1, 'foo') } + q = Sidekiq::Queue.new('other') + assert_equal 0, q.size + end - set = Sidekiq::ScheduledSet.new.to_a + it 'enumerates jobs in descending score order' do + # We need to enqueue more than 50 items, which is the page size when retrieving + # from Redis to ensure everything is sorted: the pages and the items withing them. + 51.times { ApiWorker.perform_in(100, 1, 'foo') } - assert_equal set.sort_by { |job| -job.score }, set - end + set = Sidekiq::ScheduledSet.new.to_a - it 'has no enqueued_at time for jobs enqueued in the future' do - job_id = ApiWorker.perform_in(100, 1, 'foo') - job = Sidekiq::ScheduledSet.new.find_job(job_id) - assert_nil job.enqueued_at - end + assert_equal set.sort_by { |job| -job.score }, set + end - it 'unwraps delayed jobs' do - Sidekiq::Extensions.enable_delay! - Sidekiq::Queue.delay.foo(1,2,3) - q = Sidekiq::Queue.new - x = q.first - assert_equal "Sidekiq::Queue.foo", x.display_class - assert_equal [1,2,3], x.display_args - end + it 'has no enqueued_at time for jobs enqueued in the future' do + job_id = ApiWorker.perform_in(100, 1, 'foo') + job = Sidekiq::ScheduledSet.new.find_job(job_id) + assert_nil job.enqueued_at + end - it 'unwraps ActiveJob jobs' do - ApiJob.perform_later(1, 2, 3) - q = Sidekiq::Queue.new - x = q.first - assert_equal "TestApi::ApiJob", x.display_class - assert_equal [1,2,3], x.display_args - end + it 'unwraps delayed jobs' do + Sidekiq::Extensions.enable_delay! + Sidekiq::Queue.delay.foo(1,2,3) + q = Sidekiq::Queue.new + x = q.first + assert_equal "Sidekiq::Queue.foo", x.display_class + assert_equal [1,2,3], x.display_args + end - it 'unwraps ActionMailer jobs' do - ApiMailer.test_email(1, 2, 3).deliver_later - q = Sidekiq::Queue.new('mailers') - x = q.first - assert_equal "TestApi::ApiMailer#test_email", x.display_class - assert_equal [1,2,3], x.display_args - end + it 'unwraps ActiveJob jobs' do + ApiJob.perform_later(1, 2, 3) + q = Sidekiq::Queue.new + x = q.first + assert_equal ApiJob.name, x.display_class + assert_equal [1,2,3], x.display_args + end - it 'has no enqueued_at time for jobs enqueued in the future' do - job_id = ApiWorker.perform_in(100, 1, 'foo') - job = Sidekiq::ScheduledSet.new.find_job(job_id) - assert_nil job.enqueued_at - end + it 'unwraps ActionMailer jobs' do + ApiMailer.test_email(1, 2, 3).deliver_later + q = Sidekiq::Queue.new('mailers') + x = q.first + assert_equal "#{ApiMailer.name}#test_email", x.display_class + assert_equal [1,2,3], x.display_args + end - it 'can delete jobs' do - q = Sidekiq::Queue.new - ApiWorker.perform_async(1, 'mike') - assert_equal 1, q.size + it 'has no enqueued_at time for jobs enqueued in the future' do + job_id = ApiWorker.perform_in(100, 1, 'foo') + job = Sidekiq::ScheduledSet.new.find_job(job_id) + assert_nil job.enqueued_at + end - x = q.first - assert_equal "TestApi::ApiWorker", x.display_class - assert_equal [1,'mike'], x.display_args + it 'can delete jobs' do + q = Sidekiq::Queue.new + ApiWorker.perform_async(1, 'mike') + assert_equal 1, q.size - assert_equal [true], q.map(&:delete) - assert_equal 0, q.size - end + x = q.first + assert_equal ApiWorker.name, x.display_class + assert_equal [1,'mike'], x.display_args - it "can move scheduled job to queue" do - remain_id = ApiWorker.perform_in(100, 1, 'jason') - job_id = ApiWorker.perform_in(100, 1, 'jason') - job = Sidekiq::ScheduledSet.new.find_job(job_id) - q = Sidekiq::Queue.new - job.add_to_queue - queued_job = q.find_job(job_id) - refute_nil queued_job - assert_equal queued_job.jid, job_id - assert_nil Sidekiq::ScheduledSet.new.find_job(job_id) - refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id) - end - - it "handles multiple scheduled jobs when moving to queue" do - jids = Sidekiq::Client.push_bulk('class' => ApiWorker, - 'args' => [[1, 'jason'], [2, 'jason']], - 'at' => Time.now.to_f) - assert_equal 2, jids.size - (remain_id, job_id) = jids - job = Sidekiq::ScheduledSet.new.find_job(job_id) - q = Sidekiq::Queue.new - job.add_to_queue - queued_job = q.find_job(job_id) - refute_nil queued_job - assert_equal queued_job.jid, job_id - assert_nil Sidekiq::ScheduledSet.new.find_job(job_id) - refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id) - end - - it 'can kill a scheduled job' do - job_id = ApiWorker.perform_in(100, 1, '{"foo":123}') - job = Sidekiq::ScheduledSet.new.find_job(job_id) - ds = Sidekiq::DeadSet.new - assert_equal 0, ds.size - job.kill - assert_equal 1, ds.size - end - - it 'can remove jobs when iterating over a sorted set' do - # scheduled jobs must be greater than SortedSet#each underlying page size - 51.times do - ApiWorker.perform_in(100, 'aaron') - end - set = Sidekiq::ScheduledSet.new - set.map(&:delete) - assert_equal set.size, 0 - end + assert_equal [true], q.map(&:delete) + assert_equal 0, q.size + end - it 'can remove jobs when iterating over a queue' do - # initial queue size must be greater than Queue#each underlying page size - 51.times do - ApiWorker.perform_async(1, 'aaron') - end - q = Sidekiq::Queue.new - q.map(&:delete) - assert_equal q.size, 0 - end + it "can move scheduled job to queue" do + remain_id = ApiWorker.perform_in(100, 1, 'jason') + job_id = ApiWorker.perform_in(100, 1, 'jason') + job = Sidekiq::ScheduledSet.new.find_job(job_id) + q = Sidekiq::Queue.new + job.add_to_queue + queued_job = q.find_job(job_id) + refute_nil queued_job + assert_equal queued_job.jid, job_id + assert_nil Sidekiq::ScheduledSet.new.find_job(job_id) + refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id) + end - it 'can find job by id in queues' do - q = Sidekiq::Queue.new - job_id = ApiWorker.perform_async(1, 'jason') - job = q.find_job(job_id) - refute_nil job - assert_equal job_id, job.jid - end + it "handles multiple scheduled jobs when moving to queue" do + jids = Sidekiq::Client.push_bulk('class' => ApiWorker, + 'args' => [[1, 'jason'], [2, 'jason']], + 'at' => Time.now.to_f) + assert_equal 2, jids.size + (remain_id, job_id) = jids + job = Sidekiq::ScheduledSet.new.find_job(job_id) + q = Sidekiq::Queue.new + job.add_to_queue + queued_job = q.find_job(job_id) + refute_nil queued_job + assert_equal queued_job.jid, job_id + assert_nil Sidekiq::ScheduledSet.new.find_job(job_id) + refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id) + end - it 'can clear a queue' do - q = Sidekiq::Queue.new - 2.times { ApiWorker.perform_async(1, 'mike') } - q.clear + it 'can kill a scheduled job' do + job_id = ApiWorker.perform_in(100, 1, '{"foo":123}') + job = Sidekiq::ScheduledSet.new.find_job(job_id) + ds = Sidekiq::DeadSet.new + assert_equal 0, ds.size + job.kill + assert_equal 1, ds.size + end - Sidekiq.redis do |conn| - refute conn.smembers('queues').include?('foo') - refute conn.exists('queue:foo') - end + it 'can remove jobs when iterating over a sorted set' do + # scheduled jobs must be greater than SortedSet#each underlying page size + 51.times do + ApiWorker.perform_in(100, 'aaron') end + set = Sidekiq::ScheduledSet.new + set.map(&:delete) + assert_equal set.size, 0 + end - it 'can fetch by score' do - same_time = Time.now.to_f - add_retry('bob1', same_time) - add_retry('bob2', same_time) - r = Sidekiq::RetrySet.new - assert_equal 2, r.fetch(same_time).size + it 'can remove jobs when iterating over a queue' do + # initial queue size must be greater than Queue#each underlying page size + 51.times do + ApiWorker.perform_async(1, 'aaron') end + q = Sidekiq::Queue.new + q.map(&:delete) + assert_equal q.size, 0 + end - it 'can fetch by score and jid' do - same_time = Time.now.to_f - add_retry('bob1', same_time) - add_retry('bob2', same_time) - r = Sidekiq::RetrySet.new - assert_equal 1, r.fetch(same_time, 'bob1').size - end + it 'can find job by id in queues' do + q = Sidekiq::Queue.new + job_id = ApiWorker.perform_async(1, 'jason') + job = q.find_job(job_id) + refute_nil job + assert_equal job_id, job.jid + end + + it 'can clear a queue' do + q = Sidekiq::Queue.new + 2.times { ApiWorker.perform_async(1, 'mike') } + q.clear - it 'shows empty retries' do - r = Sidekiq::RetrySet.new - assert_equal 0, r.size + Sidekiq.redis do |conn| + refute conn.smembers('queues').include?('foo') + refute conn.exists('queue:foo') end + end - it 'can enumerate retries' do - add_retry + it 'can fetch by score' do + same_time = Time.now.to_f + add_retry('bob1', same_time) + add_retry('bob2', same_time) + r = Sidekiq::RetrySet.new + assert_equal 2, r.fetch(same_time).size + end - r = Sidekiq::RetrySet.new - assert_equal 1, r.size - array = r.to_a - assert_equal 1, array.size + it 'can fetch by score and jid' do + same_time = Time.now.to_f + add_retry('bob1', same_time) + add_retry('bob2', same_time) + r = Sidekiq::RetrySet.new + assert_equal 1, r.fetch(same_time, 'bob1').size + end - retri = array.first - assert_equal 'ApiWorker', retri.klass - assert_equal 'default', retri.queue - assert_equal 'bob', retri.jid - assert_in_delta Time.now.to_f, retri.at.to_f, 0.02 - end + it 'shows empty retries' do + r = Sidekiq::RetrySet.new + assert_equal 0, r.size + end - it 'requires a jid to delete an entry' do - start_time = Time.now.to_f - add_retry('bob2', Time.now.to_f) - assert_raises(ArgumentError) do - Sidekiq::RetrySet.new.delete(start_time) - end - end + it 'can enumerate retries' do + add_retry - it 'can delete a single retry from score and jid' do - same_time = Time.now.to_f - add_retry('bob1', same_time) - add_retry('bob2', same_time) - r = Sidekiq::RetrySet.new - assert_equal 2, r.size - Sidekiq::RetrySet.new.delete(same_time, 'bob1') - assert_equal 1, r.size - end - - it 'can retry a retry' do - add_retry - r = Sidekiq::RetrySet.new - assert_equal 1, r.size - r.first.retry - assert_equal 0, r.size - assert_equal 1, Sidekiq::Queue.new('default').size - job = Sidekiq::Queue.new('default').first - assert_equal 'bob', job.jid - assert_equal 1, job['retry_count'] - end - - it 'can clear retries' do - add_retry - add_retry('test') - r = Sidekiq::RetrySet.new - assert_equal 2, r.size - r.clear - assert_equal 0, r.size - end - - it 'can enumerate processes' do - identity_string = "identity_string" - odata = { - 'pid' => 123, - 'hostname' => Socket.gethostname, - 'key' => identity_string, - 'identity' => identity_string, - 'started_at' => Time.now.to_f - 15, - } - - time = Time.now.to_f - Sidekiq.redis do |conn| - conn.multi do - conn.sadd('processes', odata['key']) - conn.hmset(odata['key'], 'info', Sidekiq.dump_json(odata), 'busy', 10, 'beat', time) - conn.sadd('processes', 'fake:pid') - end - end + r = Sidekiq::RetrySet.new + assert_equal 1, r.size + array = r.to_a + assert_equal 1, array.size - ps = Sidekiq::ProcessSet.new.to_a - assert_equal 1, ps.size - data = ps.first - assert_equal 10, data['busy'] - assert_equal time, data['beat'] - assert_equal 123, data['pid'] - data.quiet! - data.stop! - signals_string = "#{odata['key']}-signals" - assert_equal "TERM", Sidekiq.redis{|c| c.lpop(signals_string) } - assert_equal "TSTP", Sidekiq.redis{|c| c.lpop(signals_string) } - end - - it 'can enumerate workers' do - w = Sidekiq::Workers.new - assert_equal 0, w.size - w.each do - assert false - end + retri = array.first + assert_equal 'ApiWorker', retri.klass + assert_equal 'default', retri.queue + assert_equal 'bob', retri.jid + assert_in_delta Time.now.to_f, retri.at.to_f, 0.02 + end - hn = Socket.gethostname - key = "#{hn}:#{$$}" - pdata = { 'pid' => $$, 'hostname' => hn, 'started_at' => Time.now.to_i } - Sidekiq.redis do |conn| - conn.sadd('processes', key) - conn.hmset(key, 'info', Sidekiq.dump_json(pdata), 'busy', 0, 'beat', Time.now.to_f) - end + it 'requires a jid to delete an entry' do + start_time = Time.now.to_f + add_retry('bob2', Time.now.to_f) + assert_raises(ArgumentError) do + Sidekiq::RetrySet.new.delete(start_time) + end + end - s = "#{key}:workers" - data = Sidekiq.dump_json({ 'payload' => {}, 'queue' => 'default', 'run_at' => Time.now.to_i }) - Sidekiq.redis do |c| - c.hmset(s, '1234', data) - end + it 'can delete a single retry from score and jid' do + same_time = Time.now.to_f + add_retry('bob1', same_time) + add_retry('bob2', same_time) + r = Sidekiq::RetrySet.new + assert_equal 2, r.size + Sidekiq::RetrySet.new.delete(same_time, 'bob1') + assert_equal 1, r.size + end - w.each do |p, x, y| - assert_equal key, p - assert_equal "1234", x - assert_equal 'default', y['queue'] - assert_equal Time.now.year, Time.at(y['run_at']).year - end + it 'can retry a retry' do + add_retry + r = Sidekiq::RetrySet.new + assert_equal 1, r.size + r.first.retry + assert_equal 0, r.size + assert_equal 1, Sidekiq::Queue.new('default').size + job = Sidekiq::Queue.new('default').first + assert_equal 'bob', job.jid + assert_equal 1, job['retry_count'] + end - s = "#{key}:workers" - data = Sidekiq.dump_json({ 'payload' => {}, 'queue' => 'default', 'run_at' => (Time.now.to_i - 2*60*60) }) - Sidekiq.redis do |c| - c.multi do - c.hmset(s, '5678', data) - c.hmset("b#{s}", '5678', data) - end - end + it 'can clear retries' do + add_retry + add_retry('test') + r = Sidekiq::RetrySet.new + assert_equal 2, r.size + r.clear + assert_equal 0, r.size + end - assert_equal ['1234', '5678'], w.map { |_, tid, _| tid } + it 'can enumerate processes' do + identity_string = "identity_string" + odata = { + 'pid' => 123, + 'hostname' => Socket.gethostname, + 'key' => identity_string, + 'identity' => identity_string, + 'started_at' => Time.now.to_f - 15, + } + + time = Time.now.to_f + Sidekiq.redis do |conn| + conn.multi do + conn.sadd('processes', odata['key']) + conn.hmset(odata['key'], 'info', Sidekiq.dump_json(odata), 'busy', 10, 'beat', time) + conn.sadd('processes', 'fake:pid') + end + end + + ps = Sidekiq::ProcessSet.new.to_a + assert_equal 1, ps.size + data = ps.first + assert_equal 10, data['busy'] + assert_equal time, data['beat'] + assert_equal 123, data['pid'] + data.quiet! + data.stop! + signals_string = "#{odata['key']}-signals" + assert_equal "TERM", Sidekiq.redis{|c| c.lpop(signals_string) } + assert_equal "TSTP", Sidekiq.redis{|c| c.lpop(signals_string) } + end + + it 'can enumerate workers' do + w = Sidekiq::Workers.new + assert_equal 0, w.size + w.each do + assert false + end + + hn = Socket.gethostname + key = "#{hn}:#{$$}" + pdata = { 'pid' => $$, 'hostname' => hn, 'started_at' => Time.now.to_i } + Sidekiq.redis do |conn| + conn.sadd('processes', key) + conn.hmset(key, 'info', Sidekiq.dump_json(pdata), 'busy', 0, 'beat', Time.now.to_f) end - it 'can reschedule jobs' do - add_retry('foo1') - add_retry('foo2') + s = "#{key}:workers" + data = Sidekiq.dump_json({ 'payload' => {}, 'queue' => 'default', 'run_at' => Time.now.to_i }) + Sidekiq.redis do |c| + c.hmset(s, '1234', data) + end - retries = Sidekiq::RetrySet.new - assert_equal 2, retries.size - refute(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?) + w.each do |p, x, y| + assert_equal key, p + assert_equal "1234", x + assert_equal 'default', y['queue'] + assert_equal Time.now.year, Time.at(y['run_at']).year + end - retries.each do |retri| - retri.reschedule(Time.now.to_f + 10) if retri.jid == 'foo2' + s = "#{key}:workers" + data = Sidekiq.dump_json({ 'payload' => {}, 'queue' => 'default', 'run_at' => (Time.now.to_i - 2*60*60) }) + Sidekiq.redis do |c| + c.multi do + c.hmset(s, '5678', data) + c.hmset("b#{s}", '5678', data) end + end + + assert_equal ['1234', '5678'], w.map { |_, tid, _| tid } + end + + it 'can reschedule jobs' do + add_retry('foo1') + add_retry('foo2') + + retries = Sidekiq::RetrySet.new + assert_equal 2, retries.size + refute(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?) - assert_equal 2, retries.size - assert(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?) + retries.each do |retri| + retri.reschedule(Time.now.to_f + 10) if retri.jid == 'foo2' end - it 'prunes processes which have died' do - data = { 'pid' => rand(10_000), 'hostname' => "app#{rand(1_000)}", 'started_at' => Time.now.to_f } - key = "#{data['hostname']}:#{data['pid']}" - Sidekiq.redis do |conn| - conn.sadd('processes', key) - conn.hmset(key, 'info', Sidekiq.dump_json(data), 'busy', 0, 'beat', Time.now.to_f) - end + assert_equal 2, retries.size + assert(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?) + end - ps = Sidekiq::ProcessSet.new - assert_equal 1, ps.size - assert_equal 1, ps.to_a.size + it 'prunes processes which have died' do + data = { 'pid' => rand(10_000), 'hostname' => "app#{rand(1_000)}", 'started_at' => Time.now.to_f } + key = "#{data['hostname']}:#{data['pid']}" + Sidekiq.redis do |conn| + conn.sadd('processes', key) + conn.hmset(key, 'info', Sidekiq.dump_json(data), 'busy', 0, 'beat', Time.now.to_f) + end - Sidekiq.redis do |conn| - conn.sadd('processes', "bar:987") - conn.sadd('processes', "bar:986") - end + ps = Sidekiq::ProcessSet.new + assert_equal 1, ps.size + assert_equal 1, ps.to_a.size - ps = Sidekiq::ProcessSet.new - assert_equal 1, ps.size - assert_equal 1, ps.to_a.size + Sidekiq.redis do |conn| + conn.sadd('processes', "bar:987") + conn.sadd('processes', "bar:986") end - def add_retry(jid = 'bob', at = Time.now.to_f) - payload = Sidekiq.dump_json('class' => 'ApiWorker', 'args' => [1, 'mike'], 'queue' => 'default', 'jid' => jid, 'retry_count' => 2, 'failed_at' => Time.now.to_f) - Sidekiq.redis do |conn| - conn.zadd('retry', at.to_s, payload) - end + ps = Sidekiq::ProcessSet.new + assert_equal 1, ps.size + assert_equal 1, ps.to_a.size + end + + def add_retry(jid = 'bob', at = Time.now.to_f) + payload = Sidekiq.dump_json('class' => 'ApiWorker', 'args' => [1, 'mike'], 'queue' => 'default', 'jid' => jid, 'retry_count' => 2, 'failed_at' => Time.now.to_f) + Sidekiq.redis do |conn| + conn.zadd('retry', at.to_s, payload) end end end diff --git a/test/test_cli.rb b/test/test_cli.rb index e84836442..dcac86eb5 100644 --- a/test/test_cli.rb +++ b/test/test_cli.rb @@ -3,7 +3,7 @@ require_relative 'helper' require 'sidekiq/cli' -class TestCLI < Minitest::Test +describe Sidekiq::CLI do describe '#parse' do before do Sidekiq.options = Sidekiq::DEFAULTS.dup diff --git a/test/test_client.rb b/test/test_client.rb index 11e31bf61..e51c9ca9e 100644 --- a/test/test_client.rb +++ b/test/test_client.rb @@ -2,7 +2,7 @@ require_relative 'helper' require 'sidekiq/api' -class TestClient < Minitest::Test +describe Sidekiq::Client do describe 'errors' do it 'raises ArgumentError with invalid params' do assert_raises ArgumentError do diff --git a/test/test_dead_set.rb b/test/test_dead_set.rb index 243a394a8..e6e1d7be1 100644 --- a/test/test_dead_set.rb +++ b/test/test_dead_set.rb @@ -2,49 +2,45 @@ require_relative 'helper' require 'sidekiq/api' -class TestDeadSet < Minitest::Test - describe 'dead_set' do - describe 'zomg' do - def dead_set - Sidekiq::DeadSet.new - end - - it 'should put passed serialized job to the "dead" sorted set' do - serialized_job = Sidekiq.dump_json(jid: '123123', class: 'SomeWorker', args: []) - dead_set.kill(serialized_job) - - assert_equal dead_set.find_job('123123').value, serialized_job - end +describe 'DeadSet' do + def dead_set + Sidekiq::DeadSet.new + end - it 'should remove dead jobs older than Sidekiq::DeadSet.timeout' do - Sidekiq::DeadSet.stub(:timeout, 10) do - Time.stub(:now, Time.now - 11) do - dead_set.kill(Sidekiq.dump_json(jid: '000103', class: 'MyWorker3', args: [])) # the oldest - end + it 'should put passed serialized job to the "dead" sorted set' do + serialized_job = Sidekiq.dump_json(jid: '123123', class: 'SomeWorker', args: []) + dead_set.kill(serialized_job) - Time.stub(:now, Time.now - 9) do - dead_set.kill(Sidekiq.dump_json(jid: '000102', class: 'MyWorker2', args: [])) - end + assert_equal dead_set.find_job('123123').value, serialized_job + end - dead_set.kill(Sidekiq.dump_json(jid: '000101', class: 'MyWorker1', args: [])) - end + it 'should remove dead jobs older than Sidekiq::DeadSet.timeout' do + Sidekiq::DeadSet.stub(:timeout, 10) do + Time.stub(:now, Time.now - 11) do + dead_set.kill(Sidekiq.dump_json(jid: '000103', class: 'MyWorker3', args: [])) # the oldest + end - assert_nil dead_set.find_job('000103') - assert dead_set.find_job('000102') - assert dead_set.find_job('000101') + Time.stub(:now, Time.now - 9) do + dead_set.kill(Sidekiq.dump_json(jid: '000102', class: 'MyWorker2', args: [])) end - it 'should remove all but last Sidekiq::DeadSet.max_jobs-1 jobs' do - Sidekiq::DeadSet.stub(:max_jobs, 3) do - dead_set.kill(Sidekiq.dump_json(jid: '000101', class: 'MyWorker1', args: [])) - dead_set.kill(Sidekiq.dump_json(jid: '000102', class: 'MyWorker2', args: [])) - dead_set.kill(Sidekiq.dump_json(jid: '000103', class: 'MyWorker3', args: [])) - end + dead_set.kill(Sidekiq.dump_json(jid: '000101', class: 'MyWorker1', args: [])) + end - assert_nil dead_set.find_job('000101') - assert dead_set.find_job('000102') - assert dead_set.find_job('000103') - end + assert_nil dead_set.find_job('000103') + assert dead_set.find_job('000102') + assert dead_set.find_job('000101') + end + + it 'should remove all but last Sidekiq::DeadSet.max_jobs-1 jobs' do + Sidekiq::DeadSet.stub(:max_jobs, 3) do + dead_set.kill(Sidekiq.dump_json(jid: '000101', class: 'MyWorker1', args: [])) + dead_set.kill(Sidekiq.dump_json(jid: '000102', class: 'MyWorker2', args: [])) + dead_set.kill(Sidekiq.dump_json(jid: '000103', class: 'MyWorker3', args: [])) end + + assert_nil dead_set.find_job('000101') + assert dead_set.find_job('000102') + assert dead_set.find_job('000103') end end diff --git a/test/test_exception_handler.rb b/test/test_exception_handler.rb index 0ba532357..cb945cdf9 100644 --- a/test/test_exception_handler.rb +++ b/test/test_exception_handler.rb @@ -17,7 +17,7 @@ def invoke_exception(args) end end -class TestExceptionHandler < Minitest::Test +describe Sidekiq::ExceptionHandler do describe "with mock logger" do before do @old_logger = Sidekiq.logger diff --git a/test/test_extensions.rb b/test/test_extensions.rb index 183a64250..1be784765 100644 --- a/test/test_extensions.rb +++ b/test/test_extensions.rb @@ -5,115 +5,112 @@ require 'action_mailer' Sidekiq::Extensions.enable_delay! -class TestExtensions < Minitest::Test - describe 'sidekiq extensions' do - before do - Sidekiq.redis {|c| c.flushdb } - end +describe Sidekiq::Extensions do + before do + Sidekiq.redis {|c| c.flushdb } + end - class MyModel < ActiveRecord::Base - def self.long_class_method - raise "Should not be called!" - end + class MyModel < ActiveRecord::Base + def self.long_class_method + raise "Should not be called!" end + end - it 'allows delayed execution of ActiveRecord class methods' do - assert_equal [], Sidekiq::Queue.all.map(&:name) - q = Sidekiq::Queue.new - assert_equal 0, q.size - MyModel.delay.long_class_method - assert_equal ['default'], Sidekiq::Queue.all.map(&:name) - assert_equal 1, q.size - end + it 'allows delayed execution of ActiveRecord class methods' do + assert_equal [], Sidekiq::Queue.all.map(&:name) + q = Sidekiq::Queue.new + assert_equal 0, q.size + MyModel.delay.long_class_method + assert_equal ['default'], Sidekiq::Queue.all.map(&:name) + assert_equal 1, q.size + end - it 'uses and stringifies specified options' do - assert_equal [], Sidekiq::Queue.all.map(&:name) - q = Sidekiq::Queue.new('notdefault') - assert_equal 0, q.size - MyModel.delay(queue: :notdefault).long_class_method - assert_equal ['notdefault'], Sidekiq::Queue.all.map(&:name) - assert_equal 1, q.size - end + it 'uses and stringifies specified options' do + assert_equal [], Sidekiq::Queue.all.map(&:name) + q = Sidekiq::Queue.new('notdefault') + assert_equal 0, q.size + MyModel.delay(queue: :notdefault).long_class_method + assert_equal ['notdefault'], Sidekiq::Queue.all.map(&:name) + assert_equal 1, q.size + end - it 'allows delayed scheduling of AR class methods' do - ss = Sidekiq::ScheduledSet.new - assert_equal 0, ss.size - MyModel.delay_for(5.days).long_class_method - assert_equal 1, ss.size - end + it 'allows delayed scheduling of AR class methods' do + ss = Sidekiq::ScheduledSet.new + assert_equal 0, ss.size + MyModel.delay_for(5.days).long_class_method + assert_equal 1, ss.size + end - it 'allows until delayed scheduling of AR class methods' do - ss = Sidekiq::ScheduledSet.new - assert_equal 0, ss.size - MyModel.delay_until(1.day.from_now).long_class_method - assert_equal 1, ss.size - end + it 'allows until delayed scheduling of AR class methods' do + ss = Sidekiq::ScheduledSet.new + assert_equal 0, ss.size + MyModel.delay_until(1.day.from_now).long_class_method + assert_equal 1, ss.size + end - class UserMailer < ActionMailer::Base - def greetings(a, b) - raise "Should not be called!" - end + class UserMailer < ActionMailer::Base + def greetings(a, b) + raise "Should not be called!" end + end - it 'allows delayed delivery of ActionMailer mails' do - assert_equal [], Sidekiq::Queue.all.map(&:name) - q = Sidekiq::Queue.new - assert_equal 0, q.size - UserMailer.delay.greetings(1, 2) - assert_equal ['default'], Sidekiq::Queue.all.map(&:name) - assert_equal 1, q.size - end + it 'allows delayed delivery of ActionMailer mails' do + assert_equal [], Sidekiq::Queue.all.map(&:name) + q = Sidekiq::Queue.new + assert_equal 0, q.size + UserMailer.delay.greetings(1, 2) + assert_equal ['default'], Sidekiq::Queue.all.map(&:name) + assert_equal 1, q.size + end - it 'allows delayed scheduling of AM mails' do - ss = Sidekiq::ScheduledSet.new - assert_equal 0, ss.size - UserMailer.delay_for(5.days).greetings(1, 2) - assert_equal 1, ss.size - end + it 'allows delayed scheduling of AM mails' do + ss = Sidekiq::ScheduledSet.new + assert_equal 0, ss.size + UserMailer.delay_for(5.days).greetings(1, 2) + assert_equal 1, ss.size + end - it 'allows until delay scheduling of AM mails' do - ss = Sidekiq::ScheduledSet.new - assert_equal 0, ss.size - UserMailer.delay_until(5.days.from_now).greetings(1, 2) - assert_equal 1, ss.size - end + it 'allows until delay scheduling of AM mails' do + ss = Sidekiq::ScheduledSet.new + assert_equal 0, ss.size + UserMailer.delay_until(5.days.from_now).greetings(1, 2) + assert_equal 1, ss.size + end - class SomeClass - def self.doit(arg) - end + class SomeClass + def self.doit(arg) end + end - it 'allows delay of any ole class method' do - q = Sidekiq::Queue.new - assert_equal 0, q.size - SomeClass.delay.doit(Date.today) - assert_equal 1, q.size - end + it 'allows delay of any ole class method' do + q = Sidekiq::Queue.new + assert_equal 0, q.size + SomeClass.delay.doit(Date.today) + assert_equal 1, q.size + end - module SomeModule - def self.doit(arg) - end + module SomeModule + def self.doit(arg) end + end - it 'logs large payloads' do - output = capture_logging(Logger::WARN) do - SomeClass.delay.doit('a' * 8192) - end - assert_match(/#{SomeClass}.doit job argument is/, output) + it 'logs large payloads' do + output = capture_logging(Logger::WARN) do + SomeClass.delay.doit('a' * 8192) end + assert_match(/#{SomeClass}.doit job argument is/, output) + end - it 'allows delay of any module class method' do - q = Sidekiq::Queue.new - assert_equal 0, q.size - SomeModule.delay.doit(Date.today) - assert_equal 1, q.size - end + it 'allows delay of any module class method' do + q = Sidekiq::Queue.new + assert_equal 0, q.size + SomeModule.delay.doit(Date.today) + assert_equal 1, q.size + end - it 'allows Psych to serialize anonymous structs' do - obj = Struct.new(:attribute).new(my: 'data') + it 'allows Psych to serialize anonymous structs' do + obj = Struct.new(:attribute).new(my: 'data') - assert_equal obj.attribute, Psych.load(Psych.dump(obj)).attribute - end + assert_equal obj.attribute, Psych.load(Psych.dump(obj)).attribute end - end diff --git a/test/test_fetch.rb b/test/test_fetch.rb index 503eabe26..e8c8f4375 100644 --- a/test/test_fetch.rb +++ b/test/test_fetch.rb @@ -3,44 +3,42 @@ require 'sidekiq/fetch' require 'sidekiq/api' -class TestFetcher < Minitest::Test - describe 'fetcher' do - before do - Sidekiq.redis do |conn| - conn.flushdb - conn.rpush('queue:basic', 'msg') - end - end - - it 'retrieves' do - fetch = Sidekiq::BasicFetch.new(:queues => ['basic', 'bar']) - uow = fetch.retrieve_work - refute_nil uow - assert_equal 'basic', uow.queue_name - assert_equal 'msg', uow.job - q = Sidekiq::Queue.new('basic') - assert_equal 0, q.size - uow.requeue - assert_equal 1, q.size - assert_nil uow.acknowledge +describe Sidekiq::BasicFetch do + before do + Sidekiq.redis do |conn| + conn.flushdb + conn.rpush('queue:basic', 'msg') end + end - it 'retrieves with strict setting' do - fetch = Sidekiq::BasicFetch.new(:queues => ['basic', 'bar', 'bar'], :strict => true) - cmd = fetch.queues_cmd - assert_equal cmd, ['queue:basic', 'queue:bar', Sidekiq::BasicFetch::TIMEOUT] - end + it 'retrieves' do + fetch = Sidekiq::BasicFetch.new(:queues => ['basic', 'bar']) + uow = fetch.retrieve_work + refute_nil uow + assert_equal 'basic', uow.queue_name + assert_equal 'msg', uow.job + q = Sidekiq::Queue.new('basic') + assert_equal 0, q.size + uow.requeue + assert_equal 1, q.size + assert_nil uow.acknowledge + end - it 'bulk requeues' do - q1 = Sidekiq::Queue.new('foo') - q2 = Sidekiq::Queue.new('bar') - assert_equal 0, q1.size - assert_equal 0, q2.size - uow = Sidekiq::BasicFetch::UnitOfWork - Sidekiq::BasicFetch.bulk_requeue([uow.new('fuzzy:queue:foo', 'bob'), uow.new('fuzzy:queue:foo', 'bar'), uow.new('fuzzy:queue:bar', 'widget')], {:queues => []}) - assert_equal 2, q1.size - assert_equal 1, q2.size - end + it 'retrieves with strict setting' do + fetch = Sidekiq::BasicFetch.new(:queues => ['basic', 'bar', 'bar'], :strict => true) + cmd = fetch.queues_cmd + assert_equal cmd, ['queue:basic', 'queue:bar', Sidekiq::BasicFetch::TIMEOUT] + end + it 'bulk requeues' do + q1 = Sidekiq::Queue.new('foo') + q2 = Sidekiq::Queue.new('bar') + assert_equal 0, q1.size + assert_equal 0, q2.size + uow = Sidekiq::BasicFetch::UnitOfWork + Sidekiq::BasicFetch.bulk_requeue([uow.new('fuzzy:queue:foo', 'bob'), uow.new('fuzzy:queue:foo', 'bar'), uow.new('fuzzy:queue:bar', 'widget')], {:queues => []}) + assert_equal 2, q1.size + assert_equal 1, q2.size end + end diff --git a/test/test_launcher.rb b/test/test_launcher.rb index 95108d42a..808bd0a9f 100644 --- a/test/test_launcher.rb +++ b/test/test_launcher.rb @@ -3,91 +3,88 @@ require 'sidekiq/launcher' require 'sidekiq/cli' -class TestLauncher < Minitest::Test +describe Sidekiq::Launcher do + before do + Sidekiq.redis {|c| c.flushdb } + end + + def new_manager(opts) + Sidekiq::Manager.new(opts) + end - describe 'launcher' do + describe 'heartbeat' do before do - Sidekiq.redis {|c| c.flushdb } + @mgr = new_manager(options) + @launcher = Sidekiq::Launcher.new(options) + @launcher.manager = @mgr + @id = @launcher.identity + + Sidekiq::Processor::WORKER_STATE.set('a', {'b' => 1}) + + @proctitle = $0 end - def new_manager(opts) - Sidekiq::Manager.new(opts) + after do + Sidekiq::Processor::WORKER_STATE.clear + $0 = @proctitle end - describe 'heartbeat' do - before do - @mgr = new_manager(options) - @launcher = Sidekiq::Launcher.new(options) - @launcher.manager = @mgr - @id = @launcher.identity + it 'fires new heartbeat events' do + i = 0 + Sidekiq.on(:heartbeat) do + i += 1 + end + assert_equal 0, i + @launcher.heartbeat + assert_equal 1, i + @launcher.heartbeat + assert_equal 1, i + end - Sidekiq::Processor::WORKER_STATE.set('a', {'b' => 1}) + describe 'when manager is active' do + before do + Sidekiq::CLI::PROCTITLES << proc { "xyz" } + @launcher.heartbeat + Sidekiq::CLI::PROCTITLES.pop + end - @proctitle = $0 + it 'sets useful info to proctitle' do + assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy] xyz", $0 end - after do - Sidekiq::Processor::WORKER_STATE.clear - $0 = @proctitle + it 'stores process info in redis' do + info = Sidekiq.redis { |c| c.hmget(@id, 'busy') } + assert_equal ["1"], info + expires = Sidekiq.redis { |c| c.pttl(@id) } + assert_in_delta 60000, expires, 500 end + end - it 'fires new heartbeat events' do - i = 0 - Sidekiq.on(:heartbeat) do - i += 1 - end - assert_equal 0, i - @launcher.heartbeat - assert_equal 1, i + describe 'when manager is stopped' do + before do + @launcher.quiet @launcher.heartbeat - assert_equal 1, i end - describe 'when manager is active' do - before do - Sidekiq::CLI::PROCTITLES << proc { "xyz" } - @launcher.heartbeat - Sidekiq::CLI::PROCTITLES.pop - end - - it 'sets useful info to proctitle' do - assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy] xyz", $0 - end - - it 'stores process info in redis' do - info = Sidekiq.redis { |c| c.hmget(@id, 'busy') } - assert_equal ["1"], info - expires = Sidekiq.redis { |c| c.pttl(@id) } - assert_in_delta 60000, expires, 500 - end - end + #after do + #puts system('redis-cli -n 15 keys "*" | while read LINE ; do TTL=`redis-cli -n 15 ttl "$LINE"`; if [ "$TTL" -eq -1 ]; then echo "$LINE"; fi; done;') + #end - describe 'when manager is stopped' do - before do - @launcher.quiet - @launcher.heartbeat - end - - #after do - #puts system('redis-cli -n 15 keys "*" | while read LINE ; do TTL=`redis-cli -n 15 ttl "$LINE"`; if [ "$TTL" -eq -1 ]; then echo "$LINE"; fi; done;') - #end - - it 'indicates stopping status in proctitle' do - assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy] stopping", $0 - end - - it 'stores process info in redis' do - info = Sidekiq.redis { |c| c.hmget(@id, 'busy') } - assert_equal ["1"], info - expires = Sidekiq.redis { |c| c.pttl(@id) } - assert_in_delta 60000, expires, 50 - end + it 'indicates stopping status in proctitle' do + assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy] stopping", $0 end - end - def options - { :concurrency => 3, :queues => ['default'], :tag => 'myapp' } + it 'stores process info in redis' do + info = Sidekiq.redis { |c| c.hmget(@id, 'busy') } + assert_equal ["1"], info + expires = Sidekiq.redis { |c| c.pttl(@id) } + assert_in_delta 60000, expires, 50 + end end + end + def options + { :concurrency => 3, :queues => ['default'], :tag => 'myapp' } end + end diff --git a/test/test_logging.rb b/test/test_logging.rb index 7a9d694e1..7789cd5a1 100644 --- a/test/test_logging.rb +++ b/test/test_logging.rb @@ -2,34 +2,32 @@ require_relative 'helper' require 'sidekiq/logging' -class TestLogging < Minitest::Test - describe Sidekiq::Logging do - describe "#with_context" do - def ctx - Sidekiq::Logging.logger.formatter.context - end +describe Sidekiq::Logging do + describe "#with_context" do + def ctx + Sidekiq::Logging.logger.formatter.context + end - it "has no context by default" do - assert_nil ctx - end + it "has no context by default" do + assert_nil ctx + end - it "can add a context" do - Sidekiq::Logging.with_context "xx" do - assert_equal " xx", ctx - end - assert_nil ctx + it "can add a context" do + Sidekiq::Logging.with_context "xx" do + assert_equal " xx", ctx end + assert_nil ctx + end - it "can use multiple contexts" do - Sidekiq::Logging.with_context "xx" do - assert_equal " xx", ctx - Sidekiq::Logging.with_context "yy" do - assert_equal " xx yy", ctx - end - assert_equal " xx", ctx + it "can use multiple contexts" do + Sidekiq::Logging.with_context "xx" do + assert_equal " xx", ctx + Sidekiq::Logging.with_context "yy" do + assert_equal " xx yy", ctx end - assert_nil ctx + assert_equal " xx", ctx end + assert_nil ctx end end end diff --git a/test/test_manager.rb b/test/test_manager.rb index 5b52762cf..b3870c084 100644 --- a/test/test_manager.rb +++ b/test/test_manager.rb @@ -2,49 +2,46 @@ require_relative 'helper' require 'sidekiq/manager' -class TestManager < Minitest::Test - - describe 'manager' do - before do - Sidekiq.redis {|c| c.flushdb } - end - - def new_manager(opts) - Sidekiq::Manager.new(opts) - end +describe Sidekiq::Manager do + before do + Sidekiq.redis {|c| c.flushdb } + end - it 'creates N processor instances' do - mgr = new_manager(options) - assert_equal options[:concurrency], mgr.workers.size - end + def new_manager(opts) + Sidekiq::Manager.new(opts) + end - it 'shuts down the system' do - mgr = new_manager(options) - mgr.stop(::Process.clock_gettime(::Process::CLOCK_MONOTONIC)) - end + it 'creates N processor instances' do + mgr = new_manager(options) + assert_equal options[:concurrency], mgr.workers.size + end - it 'throws away dead processors' do - mgr = new_manager(options) - init_size = mgr.workers.size - processor = mgr.workers.first - begin - mgr.processor_died(processor, 'ignored') - - assert_equal init_size, mgr.workers.size - refute mgr.workers.include?(processor) - ensure - mgr.workers.each {|p| p.terminate(true) } - end - end + it 'shuts down the system' do + mgr = new_manager(options) + mgr.stop(::Process.clock_gettime(::Process::CLOCK_MONOTONIC)) + end - it 'does not support invalid concurrency' do - assert_raises(ArgumentError) { new_manager(concurrency: 0) } - assert_raises(ArgumentError) { new_manager(concurrency: -1) } + it 'throws away dead processors' do + mgr = new_manager(options) + init_size = mgr.workers.size + processor = mgr.workers.first + begin + mgr.processor_died(processor, 'ignored') + + assert_equal init_size, mgr.workers.size + refute mgr.workers.include?(processor) + ensure + mgr.workers.each {|p| p.terminate(true) } end + end - def options - { :concurrency => 3, :queues => ['default'] } - end + it 'does not support invalid concurrency' do + assert_raises(ArgumentError) { new_manager(concurrency: 0) } + assert_raises(ArgumentError) { new_manager(concurrency: -1) } + end + def options + { :concurrency => 3, :queues => ['default'] } end + end diff --git a/test/test_middleware.rb b/test/test_middleware.rb index ff5066fc8..90e1044db 100644 --- a/test/test_middleware.rb +++ b/test/test_middleware.rb @@ -3,121 +3,119 @@ require 'sidekiq/middleware/chain' require 'sidekiq/processor' -class TestMiddleware < Minitest::Test - describe 'middleware chain' do - before do - $errors = [] - end +describe Sidekiq::Middleware do + before do + $errors = [] + end - class CustomMiddleware - def initialize(name, recorder) - @name = name - @recorder = recorder - end + class CustomMiddleware + def initialize(name, recorder) + @name = name + @recorder = recorder + end - def call(*args) - @recorder << [@name, 'before'] - yield - @recorder << [@name, 'after'] - end + def call(*args) + @recorder << [@name, 'before'] + yield + @recorder << [@name, 'after'] end + end - it 'supports custom middleware' do - chain = Sidekiq::Middleware::Chain.new - chain.add CustomMiddleware, 1, [] + it 'supports custom middleware' do + chain = Sidekiq::Middleware::Chain.new + chain.add CustomMiddleware, 1, [] - assert_equal CustomMiddleware, chain.entries.last.klass - end + assert_equal CustomMiddleware, chain.entries.last.klass + end - class CustomWorker - $recorder = [] - include Sidekiq::Worker - def perform(recorder) - $recorder << ['work_performed'] - end + class CustomWorker + $recorder = [] + include Sidekiq::Worker + def perform(recorder) + $recorder << ['work_performed'] end + end - class NonYieldingMiddleware - def call(*args) - end + class NonYieldingMiddleware + def call(*args) end + end - class AnotherCustomMiddleware - def initialize(name, recorder) - @name = name - @recorder = recorder - end - - def call(*args) - @recorder << [@name, 'before'] - yield - @recorder << [@name, 'after'] - end + class AnotherCustomMiddleware + def initialize(name, recorder) + @name = name + @recorder = recorder end - class YetAnotherCustomMiddleware - def initialize(name, recorder) - @name = name - @recorder = recorder - end + def call(*args) + @recorder << [@name, 'before'] + yield + @recorder << [@name, 'after'] + end + end - def call(*args) - @recorder << [@name, 'before'] - yield - @recorder << [@name, 'after'] - end + class YetAnotherCustomMiddleware + def initialize(name, recorder) + @name = name + @recorder = recorder end - it 'executes middleware in the proper order' do - msg = Sidekiq.dump_json({ 'class' => CustomWorker.to_s, 'args' => [$recorder] }) + def call(*args) + @recorder << [@name, 'before'] + yield + @recorder << [@name, 'after'] + end + end - Sidekiq.server_middleware do |chain| - # should only add once, second should replace the first - 2.times { |i| chain.add CustomMiddleware, i.to_s, $recorder } - chain.insert_before CustomMiddleware, AnotherCustomMiddleware, '2', $recorder - chain.insert_after AnotherCustomMiddleware, YetAnotherCustomMiddleware, '3', $recorder - end + it 'executes middleware in the proper order' do + msg = Sidekiq.dump_json({ 'class' => CustomWorker.to_s, 'args' => [$recorder] }) - boss = Minitest::Mock.new - boss.expect(:options, {:queues => ['default'] }, []) - boss.expect(:options, {:queues => ['default'] }, []) - boss.expect(:options, {:queues => ['default'] }, []) - processor = Sidekiq::Processor.new(boss) - boss.expect(:processor_done, nil, [processor]) - processor.process(Sidekiq::BasicFetch::UnitOfWork.new('queue:default', msg)) - assert_equal %w(2 before 3 before 1 before work_performed 1 after 3 after 2 after), $recorder.flatten + Sidekiq.server_middleware do |chain| + # should only add once, second should replace the first + 2.times { |i| chain.add CustomMiddleware, i.to_s, $recorder } + chain.insert_before CustomMiddleware, AnotherCustomMiddleware, '2', $recorder + chain.insert_after AnotherCustomMiddleware, YetAnotherCustomMiddleware, '3', $recorder end - it 'correctly replaces middleware when using middleware with options in the initializer' do - chain = Sidekiq::Middleware::Chain.new - chain.add NonYieldingMiddleware - chain.add NonYieldingMiddleware, {:foo => 5} - assert_equal 1, chain.count - end + boss = Minitest::Mock.new + boss.expect(:options, {:queues => ['default'] }, []) + boss.expect(:options, {:queues => ['default'] }, []) + boss.expect(:options, {:queues => ['default'] }, []) + processor = Sidekiq::Processor.new(boss) + boss.expect(:processor_done, nil, [processor]) + processor.process(Sidekiq::BasicFetch::UnitOfWork.new('queue:default', msg)) + assert_equal %w(2 before 3 before 1 before work_performed 1 after 3 after 2 after), $recorder.flatten + end - it 'correctly prepends middleware' do - chain = Sidekiq::Middleware::Chain.new - chain_entries = chain.entries - chain.add CustomMiddleware - chain.prepend YetAnotherCustomMiddleware - assert_equal YetAnotherCustomMiddleware, chain_entries.first.klass - assert_equal CustomMiddleware, chain_entries.last.klass - end + it 'correctly replaces middleware when using middleware with options in the initializer' do + chain = Sidekiq::Middleware::Chain.new + chain.add NonYieldingMiddleware + chain.add NonYieldingMiddleware, {:foo => 5} + assert_equal 1, chain.count + end - it 'allows middleware to abruptly stop processing rest of chain' do - recorder = [] - chain = Sidekiq::Middleware::Chain.new - chain.add NonYieldingMiddleware - chain.add CustomMiddleware, 1, recorder + it 'correctly prepends middleware' do + chain = Sidekiq::Middleware::Chain.new + chain_entries = chain.entries + chain.add CustomMiddleware + chain.prepend YetAnotherCustomMiddleware + assert_equal YetAnotherCustomMiddleware, chain_entries.first.klass + assert_equal CustomMiddleware, chain_entries.last.klass + end - final_action = nil - chain.invoke { final_action = true } - assert_nil final_action - assert_equal [], recorder - end + it 'allows middleware to abruptly stop processing rest of chain' do + recorder = [] + chain = Sidekiq::Middleware::Chain.new + chain.add NonYieldingMiddleware + chain.add CustomMiddleware, 1, recorder + + final_action = nil + chain.invoke { final_action = true } + assert_nil final_action + assert_equal [], recorder end - describe 'i18n' do + describe 'I18n' do before do require 'i18n' I18n.enforce_available_locales = false diff --git a/test/test_processor.rb b/test/test_processor.rb index 8132bce8e..222ace0cd 100644 --- a/test/test_processor.rb +++ b/test/test_processor.rb @@ -4,362 +4,360 @@ require 'sidekiq/cli' require 'sidekiq/processor' -class TestProcessor < Minitest::Test - TestException = Class.new(StandardError) - TEST_EXCEPTION = TestException.new("kerboom!") +describe Sidekiq::Processor do + TestProcessorException = Class.new(StandardError) + TEST_PROC_EXCEPTION = TestProcessorException.new("kerboom!") + + before do + $invokes = 0 + @mgr = Minitest::Mock.new + @mgr.expect(:options, {:queues => ['default']}) + @mgr.expect(:options, {:queues => ['default']}) + @mgr.expect(:options, {:queues => ['default']}) + @processor = ::Sidekiq::Processor.new(@mgr) + end - describe 'processor' do - before do - $invokes = 0 - @mgr = Minitest::Mock.new - @mgr.expect(:options, {:queues => ['default']}) - @mgr.expect(:options, {:queues => ['default']}) - @mgr.expect(:options, {:queues => ['default']}) - @processor = ::Sidekiq::Processor.new(@mgr) + class MockWorker + include Sidekiq::Worker + def perform(args) + raise TEST_PROC_EXCEPTION if args.to_s == 'boom' + args.pop if args.is_a? Array + $invokes += 1 end + end - class MockWorker - include Sidekiq::Worker - def perform(args) - raise TEST_EXCEPTION if args.to_s == 'boom' - args.pop if args.is_a? Array - $invokes += 1 - end - end + def work(msg, queue='queue:default') + Sidekiq::BasicFetch::UnitOfWork.new(queue, msg) + end - def work(msg, queue='queue:default') - Sidekiq::BasicFetch::UnitOfWork.new(queue, msg) - end + it 'processes as expected' do + msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] }) + @processor.process(work(msg)) + assert_equal 1, $invokes + end - it 'processes as expected' do - msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] }) + it 'executes a worker as expected' do + worker = Minitest::Mock.new + worker.expect(:perform, nil, [1, 2, 3]) + @processor.execute_job(worker, [1, 2, 3]) + end + + it 're-raises exceptions after handling' do + msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] }) + re_raise = false + + begin @processor.process(work(msg)) - assert_equal 1, $invokes + flunk "Expected exception" + rescue TestProcessorException + re_raise = true end - it 'executes a worker as expected' do - worker = Minitest::Mock.new - worker.expect(:perform, nil, [1, 2, 3]) - @processor.execute_job(worker, [1, 2, 3]) - end + assert_equal 0, $invokes + assert re_raise, "does not re-raise exceptions after handling" + end - it 're-raises exceptions after handling' do - msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] }) - re_raise = false + it 'does not modify original arguments' do + msg = { 'class' => MockWorker.to_s, 'args' => [['myarg']] } + msgstr = Sidekiq.dump_json(msg) + @mgr.expect(:processor_done, nil, [@processor]) + @processor.process(work(msgstr)) + assert_equal [['myarg']], msg['args'] + end - begin - @processor.process(work(msg)) - flunk "Expected exception" - rescue TestException - re_raise = true + describe 'exception handling' do + let(:errors) { [] } + let(:error_handler) do + proc do |exception, context| + errors << { exception: exception, context: context } end + end - assert_equal 0, $invokes - assert re_raise, "does not re-raise exceptions after handling" + before do + Sidekiq.error_handlers << error_handler end - it 'does not modify original arguments' do - msg = { 'class' => MockWorker.to_s, 'args' => [['myarg']] } - msgstr = Sidekiq.dump_json(msg) - @mgr.expect(:processor_done, nil, [@processor]) - @processor.process(work(msgstr)) - assert_equal [['myarg']], msg['args'] + after do + Sidekiq.error_handlers.pop end - describe 'exception handling' do - let(:errors) { [] } - let(:error_handler) do - proc do |exception, context| - errors << { exception: exception, context: context } - end + it 'handles invalid JSON' do + ds = Sidekiq::DeadSet.new + ds.clear + job_hash = { 'class' => MockWorker.to_s, 'args' => ['boom'] } + msg = Sidekiq.dump_json(job_hash) + job = work(msg[0...-2]) + ds = Sidekiq::DeadSet.new + assert_equal 0, ds.size + begin + @processor.instance_variable_set(:'@job', job) + @processor.process(job) + rescue JSON::ParserError end + assert_equal 1, ds.size + end - before do - Sidekiq.error_handlers << error_handler + it 'handles exceptions raised by the job' do + job_hash = { 'class' => MockWorker.to_s, 'args' => ['boom'], 'jid' => '123987123' } + msg = Sidekiq.dump_json(job_hash) + job = work(msg) + begin + @processor.instance_variable_set(:'@job', job) + @processor.process(job) + rescue TestProcessorException end + assert_equal 1, errors.count + assert_instance_of TestProcessorException, errors.first[:exception] + assert_equal msg, errors.first[:context][:jobstr] + assert_equal job_hash['jid'], errors.first[:context][:job]['jid'] + end - after do - Sidekiq.error_handlers.pop + it 'handles exceptions raised by the reloader' do + job_hash = { 'class' => MockWorker.to_s, 'args' => ['boom'] } + msg = Sidekiq.dump_json(job_hash) + @processor.instance_variable_set(:'@reloader', proc { raise TEST_PROC_EXCEPTION }) + job = work(msg) + begin + @processor.instance_variable_set(:'@job', job) + @processor.process(job) + rescue TestProcessorException end + assert_equal 1, errors.count + assert_instance_of TestProcessorException, errors.first[:exception] + assert_equal msg, errors.first[:context][:jobstr] + assert_equal job_hash, errors.first[:context][:job] + end - it 'handles invalid JSON' do - ds = Sidekiq::DeadSet.new - ds.clear - job_hash = { 'class' => MockWorker.to_s, 'args' => ['boom'] } - msg = Sidekiq.dump_json(job_hash) - job = work(msg[0...-2]) - ds = Sidekiq::DeadSet.new - assert_equal 0, ds.size - begin - @processor.instance_variable_set(:'@job', job) - @processor.process(job) - rescue JSON::ParserError + it 'handles exceptions raised during fetch' do + fetch_stub = lambda { raise StandardError, "fetch exception" } + # swallow logging because actually care about the added exception handler + capture_logging do + @processor.instance_variable_get('@strategy').stub(:retrieve_work, fetch_stub) do + @processor.process_one end - assert_equal 1, ds.size end - it 'handles exceptions raised by the job' do - job_hash = { 'class' => MockWorker.to_s, 'args' => ['boom'], 'jid' => '123987123' } - msg = Sidekiq.dump_json(job_hash) - job = work(msg) - begin - @processor.instance_variable_set(:'@job', job) - @processor.process(job) - rescue TestException - end - assert_equal 1, errors.count - assert_instance_of TestException, errors.first[:exception] - assert_equal msg, errors.first[:context][:jobstr] - assert_equal job_hash['jid'], errors.first[:context][:job]['jid'] - end + assert_instance_of StandardError, errors.last[:exception] + end + end - it 'handles exceptions raised by the reloader' do - job_hash = { 'class' => MockWorker.to_s, 'args' => ['boom'] } - msg = Sidekiq.dump_json(job_hash) - @processor.instance_variable_set(:'@reloader', proc { raise TEST_EXCEPTION }) - job = work(msg) - begin - @processor.instance_variable_set(:'@job', job) - @processor.process(job) - rescue TestException - end - assert_equal 1, errors.count - assert_instance_of TestException, errors.first[:exception] - assert_equal msg, errors.first[:context][:jobstr] - assert_equal job_hash, errors.first[:context][:job] + describe 'acknowledgement' do + class ExceptionRaisingMiddleware + def initialize(raise_before_yield, raise_after_yield, skip) + @raise_before_yield = raise_before_yield + @raise_after_yield = raise_after_yield + @skip = skip end - it 'handles exceptions raised during fetch' do - fetch_stub = lambda { raise StandardError, "fetch exception" } - # swallow logging because actually care about the added exception handler - capture_logging do - @processor.instance_variable_get('@strategy').stub(:retrieve_work, fetch_stub) do - @processor.process_one - end - end - - assert_instance_of StandardError, errors.last[:exception] + def call(worker, item, queue) + raise TEST_PROC_EXCEPTION if @raise_before_yield + yield unless @skip + raise TEST_PROC_EXCEPTION if @raise_after_yield end end - describe 'acknowledgement' do - class ExceptionRaisingMiddleware - def initialize(raise_before_yield, raise_after_yield, skip) - @raise_before_yield = raise_before_yield - @raise_after_yield = raise_after_yield - @skip = skip - end + let(:raise_before_yield) { false } + let(:raise_after_yield) { false } + let(:skip_job) { false } + let(:worker_args) { ['myarg'] } + let(:work) { MiniTest::Mock.new } - def call(worker, item, queue) - raise TEST_EXCEPTION if @raise_before_yield - yield unless @skip - raise TEST_EXCEPTION if @raise_after_yield - end + before do + work.expect(:queue_name, 'queue:default') + work.expect(:job, Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => worker_args })) + Sidekiq.server_middleware do |chain| + chain.prepend ExceptionRaisingMiddleware, raise_before_yield, raise_after_yield, skip_job end + end - let(:raise_before_yield) { false } - let(:raise_after_yield) { false } - let(:skip_job) { false } - let(:worker_args) { ['myarg'] } - let(:work) { MiniTest::Mock.new } - - before do - work.expect(:queue_name, 'queue:default') - work.expect(:job, Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => worker_args })) - Sidekiq.server_middleware do |chain| - chain.prepend ExceptionRaisingMiddleware, raise_before_yield, raise_after_yield, skip_job - end + after do + Sidekiq.server_middleware do |chain| + chain.remove ExceptionRaisingMiddleware end + work.verify + end + + describe 'middleware throws an exception before processing the work' do + let(:raise_before_yield) { true } - after do - Sidekiq.server_middleware do |chain| - chain.remove ExceptionRaisingMiddleware + it 'acks the job' do + work.expect(:acknowledge, nil) + begin + @processor.process(work) + flunk "Expected #process to raise exception" + rescue TestProcessorException end - work.verify end + end - describe 'middleware throws an exception before processing the work' do - let(:raise_before_yield) { true } + describe 'middleware throws an exception after processing the work' do + let(:raise_after_yield) { true } - it 'acks the job' do - work.expect(:acknowledge, nil) - begin - @processor.process(work) - flunk "Expected #process to raise exception" - rescue TestException - end + it 'acks the job' do + work.expect(:acknowledge, nil) + begin + @processor.process(work) + flunk "Expected #process to raise exception" + rescue TestProcessorException end end + end - describe 'middleware throws an exception after processing the work' do - let(:raise_after_yield) { true } + describe 'middleware decides to skip work' do + let(:skip_job) { true } - it 'acks the job' do - work.expect(:acknowledge, nil) - begin - @processor.process(work) - flunk "Expected #process to raise exception" - rescue TestException - end - end + it 'acks the job' do + work.expect(:acknowledge, nil) + @mgr.expect(:processor_done, nil, [@processor]) + @processor.process(work) end + end - describe 'middleware decides to skip work' do - let(:skip_job) { true } + describe 'worker raises an exception' do + let(:worker_args) { ['boom'] } - it 'acks the job' do - work.expect(:acknowledge, nil) - @mgr.expect(:processor_done, nil, [@processor]) + it 'acks the job' do + work.expect(:acknowledge, nil) + begin @processor.process(work) + flunk "Expected #process to raise exception" + rescue TestProcessorException end end + end - describe 'worker raises an exception' do - let(:worker_args) { ['boom'] } - - it 'acks the job' do - work.expect(:acknowledge, nil) - begin - @processor.process(work) - flunk "Expected #process to raise exception" - rescue TestException - end - end + describe 'everything goes well' do + it 'acks the job' do + work.expect(:acknowledge, nil) + @mgr.expect(:processor_done, nil, [@processor]) + @processor.process(work) end + end + end - describe 'everything goes well' do - it 'acks the job' do - work.expect(:acknowledge, nil) - @mgr.expect(:processor_done, nil, [@processor]) - @processor.process(work) + describe 'retry' do + class ArgsMutatingServerMiddleware + def call(worker, item, queue) + item['args'] = item['args'].map do |arg| + arg.to_sym if arg.is_a?(String) end + yield end end - describe 'retry' do - class ArgsMutatingServerMiddleware - def call(worker, item, queue) - item['args'] = item['args'].map do |arg| - arg.to_sym if arg.is_a?(String) - end - yield + class ArgsMutatingClientMiddleware + def call(worker, item, queue, redis_pool) + item['args'] = item['args'].map do |arg| + arg.to_s if arg.is_a?(Symbol) end + yield end + end - class ArgsMutatingClientMiddleware - def call(worker, item, queue, redis_pool) - item['args'] = item['args'].map do |arg| - arg.to_s if arg.is_a?(Symbol) - end - yield - end + before do + Sidekiq.server_middleware do |chain| + chain.prepend ArgsMutatingServerMiddleware end - - before do - Sidekiq.server_middleware do |chain| - chain.prepend ArgsMutatingServerMiddleware - end - Sidekiq.client_middleware do |chain| - chain.prepend ArgsMutatingClientMiddleware - end + Sidekiq.client_middleware do |chain| + chain.prepend ArgsMutatingClientMiddleware end + end - after do - Sidekiq.server_middleware do |chain| - chain.remove ArgsMutatingServerMiddleware - end - Sidekiq.client_middleware do |chain| - chain.remove ArgsMutatingClientMiddleware - end + after do + Sidekiq.server_middleware do |chain| + chain.remove ArgsMutatingServerMiddleware end - - describe 'middleware mutates the job args and then fails' do - it 'requeues with original arguments' do - job_data = { 'class' => MockWorker.to_s, 'args' => ['boom'] } - - retry_stub_called = false - retry_stub = lambda { |worker, msg, queue, exception| - retry_stub_called = true - assert_equal 'boom', msg['args'].first - } - - @processor.instance_variable_get('@retrier').stub(:attempt_retry, retry_stub) do - msg = Sidekiq.dump_json(job_data) - begin - @processor.process(work(msg)) - flunk "Expected exception" - rescue TestException - end - end - - assert retry_stub_called - end + Sidekiq.client_middleware do |chain| + chain.remove ArgsMutatingClientMiddleware end end - describe 'stats' do - before do - Sidekiq.redis {|c| c.flushdb } - end + describe 'middleware mutates the job args and then fails' do + it 'requeues with original arguments' do + job_data = { 'class' => MockWorker.to_s, 'args' => ['boom'] } - describe 'when successful' do - let(:processed_today_key) { "stat:processed:#{Time.now.utc.strftime("%Y-%m-%d")}" } + retry_stub_called = false + retry_stub = lambda { |worker, msg, queue, exception| + retry_stub_called = true + assert_equal 'boom', msg['args'].first + } - def successful_job - msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] }) - @mgr.expect(:processor_done, nil, [@processor]) - @processor.process(work(msg)) + @processor.instance_variable_get('@retrier').stub(:attempt_retry, retry_stub) do + msg = Sidekiq.dump_json(job_data) + begin + @processor.process(work(msg)) + flunk "Expected exception" + rescue TestProcessorException + end end - it 'increments processed stat' do - Sidekiq::Processor::PROCESSED.reset - successful_job - assert_equal 1, Sidekiq::Processor::PROCESSED.reset - end + assert retry_stub_called end + end + end - describe 'when failed' do - let(:failed_today_key) { "stat:failed:#{Time.now.utc.strftime("%Y-%m-%d")}" } + describe 'stats' do + before do + Sidekiq.redis {|c| c.flushdb } + end - def failed_job - msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] }) - begin - @processor.process(work(msg)) - rescue TestException - end - end + describe 'when successful' do + let(:processed_today_key) { "stat:processed:#{Time.now.utc.strftime("%Y-%m-%d")}" } - it 'increments failed stat' do - Sidekiq::Processor::FAILURE.reset - failed_job - assert_equal 1, Sidekiq::Processor::FAILURE.reset - end + def successful_job + msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] }) + @mgr.expect(:processor_done, nil, [@processor]) + @processor.process(work(msg)) + end + + it 'increments processed stat' do + Sidekiq::Processor::PROCESSED.reset + successful_job + assert_equal 1, Sidekiq::Processor::PROCESSED.reset end end - describe 'custom job logger class' do - class CustomJobLogger - def call(item, queue) - yield - rescue Exception - raise + describe 'when failed' do + let(:failed_today_key) { "stat:failed:#{Time.now.utc.strftime("%Y-%m-%d")}" } + + def failed_job + msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] }) + begin + @processor.process(work(msg)) + rescue TestProcessorException end end - before do - @mgr = Minitest::Mock.new - @mgr.expect(:options, {:queues => ['default'], :job_logger => CustomJobLogger}) - @mgr.expect(:options, {:queues => ['default'], :job_logger => CustomJobLogger}) - @mgr.expect(:options, {:queues => ['default'], :job_logger => CustomJobLogger}) - @processor = ::Sidekiq::Processor.new(@mgr) + it 'increments failed stat' do + Sidekiq::Processor::FAILURE.reset + failed_job + assert_equal 1, Sidekiq::Processor::FAILURE.reset end + end + end - it 'is called instead default Sidekiq::JobLogger' do - msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] }) - @processor.process(work(msg)) - assert_equal 1, $invokes - @mgr.verify + describe 'custom job logger class' do + class CustomJobLogger + def call(item, queue) + yield + rescue Exception + raise end end + + before do + @mgr = Minitest::Mock.new + @mgr.expect(:options, {:queues => ['default'], :job_logger => CustomJobLogger}) + @mgr.expect(:options, {:queues => ['default'], :job_logger => CustomJobLogger}) + @mgr.expect(:options, {:queues => ['default'], :job_logger => CustomJobLogger}) + @processor = ::Sidekiq::Processor.new(@mgr) + end + + it 'is called instead default Sidekiq::JobLogger' do + msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] }) + @processor.process(work(msg)) + assert_equal 1, $invokes + @mgr.verify + end end end diff --git a/test/test_rails.rb b/test/test_rails.rb index caf97488b..8149a5d9a 100644 --- a/test/test_rails.rb +++ b/test/test_rails.rb @@ -1,22 +1,13 @@ # frozen_string_literal: true require_relative 'helper' +require 'active_job' -$HAS_AJ = true -begin - require 'active_job' -rescue LoadError - $HAS_AJ = false -end - -class TestRails < Minitest::Test - - describe 'ActiveJob' do - it 'does not allow Sidekiq::Worker in AJ::Base classes' do - ex = assert_raises ArgumentError do - c = Class.new(ActiveJob::Base) - c.send(:include, Sidekiq::Worker) - end - assert_includes ex.message, "cannot include" - end if $HAS_AJ +describe 'ActiveJob' do + it 'does not allow Sidekiq::Worker in AJ::Base classes' do + ex = assert_raises ArgumentError do + c = Class.new(ActiveJob::Base) + c.send(:include, Sidekiq::Worker) + end + assert_includes ex.message, "cannot include" end end diff --git a/test/test_redis_connection.rb b/test/test_redis_connection.rb index 2af17676b..6dc5ef9b7 100644 --- a/test/test_redis_connection.rb +++ b/test/test_redis_connection.rb @@ -3,8 +3,8 @@ require_relative 'helper' require 'sidekiq/cli' -class TestRedisConnection < Minitest::Test - describe ".create" do +describe Sidekiq::RedisConnection do + describe "create" do before do Sidekiq.options = Sidekiq::DEFAULTS.dup @old = ENV['REDIS_URL'] diff --git a/test/test_retry.rb b/test/test_retry.rb index b78e9aed2..c0fe2d278 100644 --- a/test/test_retry.rb +++ b/test/test_retry.rb @@ -4,7 +4,7 @@ require 'sidekiq/scheduled' require 'sidekiq/job_retry' -class TestRetry < Minitest::Test +describe Sidekiq::JobRetry do describe 'middleware' do class SomeWorker include Sidekiq::Worker diff --git a/test/test_retry_exhausted.rb b/test/test_retry_exhausted.rb index fb3fe2c77..722f6139d 100644 --- a/test/test_retry_exhausted.rb +++ b/test/test_retry_exhausted.rb @@ -2,179 +2,177 @@ require_relative 'helper' require 'sidekiq/job_retry' -class TestRetryExhausted < Minitest::Test - describe 'sidekiq_retries_exhausted' do - class NewWorker - include Sidekiq::Worker +describe 'sidekiq_retries_exhausted' do + class NewWorker + include Sidekiq::Worker - sidekiq_class_attribute :exhausted_called, :exhausted_job, :exhausted_exception + sidekiq_class_attribute :exhausted_called, :exhausted_job, :exhausted_exception - sidekiq_retries_exhausted do |job, e| - self.exhausted_called = true - self.exhausted_job = job - self.exhausted_exception = e - end + sidekiq_retries_exhausted do |job, e| + self.exhausted_called = true + self.exhausted_job = job + self.exhausted_exception = e end + end - class OldWorker - include Sidekiq::Worker + class OldWorker + include Sidekiq::Worker - sidekiq_class_attribute :exhausted_called, :exhausted_job, :exhausted_exception + sidekiq_class_attribute :exhausted_called, :exhausted_job, :exhausted_exception - sidekiq_retries_exhausted do |job| - self.exhausted_called = true - self.exhausted_job = job - end + sidekiq_retries_exhausted do |job| + self.exhausted_called = true + self.exhausted_job = job end + end - def cleanup - [NewWorker, OldWorker].each do |worker_class| - worker_class.exhausted_called = nil - worker_class.exhausted_job = nil - worker_class.exhausted_exception = nil - end + def cleanup + [NewWorker, OldWorker].each do |worker_class| + worker_class.exhausted_called = nil + worker_class.exhausted_job = nil + worker_class.exhausted_exception = nil end + end - before do - cleanup - end + before do + cleanup + end - after do - cleanup - end + after do + cleanup + end - def new_worker - @new_worker ||= NewWorker.new - end + def new_worker + @new_worker ||= NewWorker.new + end - def old_worker - @old_worker ||= OldWorker.new - end + def old_worker + @old_worker ||= OldWorker.new + end + + def handler(options={}) + @handler ||= Sidekiq::JobRetry.new(options) + end - def handler(options={}) - @handler ||= Sidekiq::JobRetry.new(options) + def job(options={}) + @job ||= {'class' => 'Bob', 'args' => [1, 2, 'foo']}.merge(options) + end + + it 'does not run exhausted block when job successful on first run' do + handler.local(new_worker, job('retry' => 2), 'default') do + # successful end - def job(options={}) - @job ||= {'class' => 'Bob', 'args' => [1, 2, 'foo']}.merge(options) + refute NewWorker.exhausted_called + end + + it 'does not run exhausted block when job successful on last retry' do + handler.local(new_worker, job('retry_count' => 0, 'retry' => 1), 'default') do + # successful end - it 'does not run exhausted block when job successful on first run' do - handler.local(new_worker, job('retry' => 2), 'default') do - # successful - end + refute NewWorker.exhausted_called + end - refute NewWorker.exhausted_called + it 'does not run exhausted block when retries not exhausted yet' do + assert_raises RuntimeError do + handler.local(new_worker, job('retry' => 1), 'default') do + raise 'kerblammo!' + end end - it 'does not run exhausted block when job successful on last retry' do + refute NewWorker.exhausted_called + end + + it 'runs exhausted block when retries exhausted' do + assert_raises RuntimeError do handler.local(new_worker, job('retry_count' => 0, 'retry' => 1), 'default') do - # successful + raise 'kerblammo!' end - - refute NewWorker.exhausted_called end - it 'does not run exhausted block when retries not exhausted yet' do - assert_raises RuntimeError do - handler.local(new_worker, job('retry' => 1), 'default') do - raise 'kerblammo!' - end - end + assert NewWorker.exhausted_called + end - refute NewWorker.exhausted_called - end - it 'runs exhausted block when retries exhausted' do - assert_raises RuntimeError do - handler.local(new_worker, job('retry_count' => 0, 'retry' => 1), 'default') do - raise 'kerblammo!' - end + it 'passes job and exception to retries exhausted block' do + raised_error = assert_raises RuntimeError do + handler.local(new_worker, job('retry_count' => 0, 'retry' => 1), 'default') do + raise 'kerblammo!' end - - assert NewWorker.exhausted_called end + raised_error = raised_error.cause + assert new_worker.exhausted_called + assert_equal raised_error.message, new_worker.exhausted_job['error_message'] + assert_equal raised_error, new_worker.exhausted_exception + end - it 'passes job and exception to retries exhausted block' do - raised_error = assert_raises RuntimeError do - handler.local(new_worker, job('retry_count' => 0, 'retry' => 1), 'default') do - raise 'kerblammo!' - end + it 'passes job to retries exhausted block' do + raised_error = assert_raises RuntimeError do + handler.local(old_worker, job('retry_count' => 0, 'retry' => 1), 'default') do + raise 'kerblammo!' end - raised_error = raised_error.cause - - assert new_worker.exhausted_called - assert_equal raised_error.message, new_worker.exhausted_job['error_message'] - assert_equal raised_error, new_worker.exhausted_exception end + raised_error = raised_error.cause + + assert old_worker.exhausted_called + assert_equal raised_error.message, old_worker.exhausted_job['error_message'] + assert_nil new_worker.exhausted_exception + end - it 'passes job to retries exhausted block' do + it 'allows a global default handler' do + begin + class Foobar + include Sidekiq::Worker + end + + exhausted_job = nil + exhausted_exception = nil + Sidekiq.default_retries_exhausted = lambda do |job, ex| + exhausted_job = job + exhausted_exception = ex + end + f = Foobar.new raised_error = assert_raises RuntimeError do - handler.local(old_worker, job('retry_count' => 0, 'retry' => 1), 'default') do + handler.local(f, job('retry_count' => 0, 'retry' => 1), 'default') do raise 'kerblammo!' end end raised_error = raised_error.cause - assert old_worker.exhausted_called - assert_equal raised_error.message, old_worker.exhausted_job['error_message'] - assert_nil new_worker.exhausted_exception + assert exhausted_job + assert_equal raised_error, exhausted_exception + ensure + Sidekiq.default_retries_exhausted = nil end + end - it 'allows a global default handler' do - begin - class Foobar - include Sidekiq::Worker - end - - exhausted_job = nil - exhausted_exception = nil - Sidekiq.default_retries_exhausted = lambda do |job, ex| - exhausted_job = job - exhausted_exception = ex - end - f = Foobar.new - raised_error = assert_raises RuntimeError do - handler.local(f, job('retry_count' => 0, 'retry' => 1), 'default') do - raise 'kerblammo!' - end - end - raised_error = raised_error.cause - - assert exhausted_job - assert_equal raised_error, exhausted_exception - ensure - Sidekiq.default_retries_exhausted = nil + it 'allows global failure handlers' do + begin + class Foobar + include Sidekiq::Worker end - end - - it 'allows global failure handlers' do - begin - class Foobar - include Sidekiq::Worker - end - exhausted_job = nil - exhausted_exception = nil - Sidekiq.death_handlers.clear - Sidekiq.death_handlers << proc do |job, ex| - exhausted_job = job - exhausted_exception = ex - end - f = Foobar.new - raised_error = assert_raises RuntimeError do - handler.local(f, job('retry_count' => 0, 'retry' => 1), 'default') do - raise 'kerblammo!' - end + exhausted_job = nil + exhausted_exception = nil + Sidekiq.death_handlers.clear + Sidekiq.death_handlers << proc do |job, ex| + exhausted_job = job + exhausted_exception = ex + end + f = Foobar.new + raised_error = assert_raises RuntimeError do + handler.local(f, job('retry_count' => 0, 'retry' => 1), 'default') do + raise 'kerblammo!' end - raised_error = raised_error.cause - - assert exhausted_job - assert_equal raised_error, exhausted_exception - ensure - Sidekiq.death_handlers.clear end + raised_error = raised_error.cause + + assert exhausted_job + assert_equal raised_error, exhausted_exception + ensure + Sidekiq.death_handlers.clear end end end diff --git a/test/test_scheduled.rb b/test/test_scheduled.rb index 3430742a9..ae2b2bf51 100644 --- a/test/test_scheduled.rb +++ b/test/test_scheduled.rb @@ -2,7 +2,7 @@ require_relative 'helper' require 'sidekiq/scheduled' -class TestScheduled < Minitest::Test +describe Sidekiq::Scheduled do class ScheduledWorker include Sidekiq::Worker def perform(x) @@ -24,14 +24,14 @@ def perform(x) @poller = Sidekiq::Scheduled::Poller.new end - class Stopper + class MyStopper def call(worker_class, job, queue, r) yield if job['args'].first.odd? end end it 'executes client middleware' do - Sidekiq.client_middleware.add Stopper + Sidekiq.client_middleware.add MyStopper begin @retry.schedule (Time.now - 60).to_f, @error_1 @retry.schedule (Time.now - 60).to_f, @error_2 @@ -45,7 +45,7 @@ def call(worker_class, job, queue, r) assert_equal 0, Sidekiq::Queue.new("queue_5").size assert_equal 1, Sidekiq::Queue.new("queue_6").size ensure - Sidekiq.client_middleware.remove Stopper + Sidekiq.client_middleware.remove MyStopper end end diff --git a/test/test_scheduling.rb b/test/test_scheduling.rb index 93464c452..bf3e74754 100644 --- a/test/test_scheduling.rb +++ b/test/test_scheduling.rb @@ -3,9 +3,9 @@ require 'sidekiq/scheduled' require 'active_support/core_ext/integer/time' -class TestScheduling < Minitest::Test +describe 'job scheduling' do describe 'middleware' do - class ScheduledWorker + class SomeScheduledWorker include Sidekiq::Worker sidekiq_options :queue => :custom_queue def perform(x) @@ -23,25 +23,25 @@ def to_f; 42.0 end assert_equal 0, ss.size - assert ScheduledWorker.perform_in(600, 'mike') + assert SomeScheduledWorker.perform_in(600, 'mike') assert_equal 1, ss.size - assert ScheduledWorker.perform_in(1.month, 'mike') + assert SomeScheduledWorker.perform_in(1.month, 'mike') assert_equal 2, ss.size - assert ScheduledWorker.perform_in(5.days.from_now, 'mike') + assert SomeScheduledWorker.perform_in(5.days.from_now, 'mike') assert_equal 3, ss.size q = Sidekiq::Queue.new("custom_queue") qs = q.size - assert ScheduledWorker.perform_in(-300, 'mike') + assert SomeScheduledWorker.perform_in(-300, 'mike') assert_equal 3, ss.size assert_equal qs+1, q.size - assert Sidekiq::Client.push_bulk('class' => ScheduledWorker, 'args' => [['mike'], ['mike']], 'at' => 600) + assert Sidekiq::Client.push_bulk('class' => SomeScheduledWorker, 'args' => [['mike'], ['mike']], 'at' => 600) assert_equal 5, ss.size - assert ScheduledWorker.perform_in(TimeDuck.new, 'samwise') + assert SomeScheduledWorker.perform_in(TimeDuck.new, 'samwise') assert_equal 6, ss.size end @@ -49,7 +49,7 @@ def to_f; 42.0 end ss = Sidekiq::ScheduledSet.new ss.clear - assert ScheduledWorker.perform_in(1.month, 'mike') + assert SomeScheduledWorker.perform_in(1.month, 'mike') job = ss.first assert job['created_at'] refute job['enqueued_at'] diff --git a/test/test_sidekiq.rb b/test/test_sidekiq.rb index fe6887e28..7a1c62565 100644 --- a/test/test_sidekiq.rb +++ b/test/test_sidekiq.rb @@ -2,7 +2,7 @@ require_relative 'helper' require 'sidekiq/cli' -class TestSidekiq < Minitest::Test +describe Sidekiq do describe 'json processing' do it 'handles json' do assert_equal({"foo" => "bar"}, Sidekiq.load_json("{\"foo\":\"bar\"}")) diff --git a/test/test_sidekiqctl.rb b/test/test_sidekiqctl.rb index 6fbb95528..3d2efafdf 100644 --- a/test/test_sidekiqctl.rb +++ b/test/test_sidekiqctl.rb @@ -20,8 +20,8 @@ def output(section = 'all') end end -class TestSidekiqctl < Minitest::Test - describe 'sidekiqctl status' do +describe Sidekiqctl do + describe 'status' do describe 'version' do it 'displays the current Sidekiq version' do assert_includes output, "Sidekiq #{Sidekiq::VERSION}" diff --git a/test/test_testing.rb b/test/test_testing.rb index eb3bc4498..e43aa4291 100644 --- a/test/test_testing.rb +++ b/test/test_testing.rb @@ -1,83 +1,81 @@ # frozen_string_literal: true require_relative 'helper' -class TestTesting < Minitest::Test - describe 'sidekiq testing' do - describe 'require/load sidekiq/testing.rb' do - before do - require 'sidekiq/testing' - end +describe 'Sidekiq::Testing' do + describe 'require/load sidekiq/testing.rb' do + before do + require 'sidekiq/testing' + end - after do - Sidekiq::Testing.disable! - end + after do + Sidekiq::Testing.disable! + end + + it 'enables fake testing' do + Sidekiq::Testing.fake! + assert Sidekiq::Testing.enabled? + assert Sidekiq::Testing.fake? + refute Sidekiq::Testing.inline? + end + + it 'enables fake testing in a block' do + Sidekiq::Testing.disable! + assert Sidekiq::Testing.disabled? + refute Sidekiq::Testing.fake? - it 'enables fake testing' do - Sidekiq::Testing.fake! + Sidekiq::Testing.fake! do assert Sidekiq::Testing.enabled? assert Sidekiq::Testing.fake? refute Sidekiq::Testing.inline? end - it 'enables fake testing in a block' do - Sidekiq::Testing.disable! - assert Sidekiq::Testing.disabled? - refute Sidekiq::Testing.fake? + refute Sidekiq::Testing.enabled? + refute Sidekiq::Testing.fake? + end - Sidekiq::Testing.fake! do - assert Sidekiq::Testing.enabled? - assert Sidekiq::Testing.fake? - refute Sidekiq::Testing.inline? - end + it 'disables testing in a block' do + Sidekiq::Testing.fake! + assert Sidekiq::Testing.fake? - refute Sidekiq::Testing.enabled? + Sidekiq::Testing.disable! do refute Sidekiq::Testing.fake? + assert Sidekiq::Testing.disabled? end - it 'disables testing in a block' do - Sidekiq::Testing.fake! - assert Sidekiq::Testing.fake? + assert Sidekiq::Testing.fake? + assert Sidekiq::Testing.enabled? + end + end - Sidekiq::Testing.disable! do - refute Sidekiq::Testing.fake? - assert Sidekiq::Testing.disabled? - end + describe 'require/load sidekiq/testing/inline.rb' do + before do + require 'sidekiq/testing/inline' + end - assert Sidekiq::Testing.fake? - assert Sidekiq::Testing.enabled? - end + after do + Sidekiq::Testing.disable! end - describe 'require/load sidekiq/testing/inline.rb' do - before do - require 'sidekiq/testing/inline' - end + it 'enables inline testing' do + Sidekiq::Testing.inline! + assert Sidekiq::Testing.enabled? + assert Sidekiq::Testing.inline? + refute Sidekiq::Testing.fake? + end - after do - Sidekiq::Testing.disable! - end + it 'enables inline testing in a block' do + Sidekiq::Testing.disable! + assert Sidekiq::Testing.disabled? + refute Sidekiq::Testing.fake? - it 'enables inline testing' do - Sidekiq::Testing.inline! + Sidekiq::Testing.inline! do assert Sidekiq::Testing.enabled? assert Sidekiq::Testing.inline? - refute Sidekiq::Testing.fake? end - it 'enables inline testing in a block' do - Sidekiq::Testing.disable! - assert Sidekiq::Testing.disabled? - refute Sidekiq::Testing.fake? - - Sidekiq::Testing.inline! do - assert Sidekiq::Testing.enabled? - assert Sidekiq::Testing.inline? - end - - refute Sidekiq::Testing.enabled? - refute Sidekiq::Testing.inline? - refute Sidekiq::Testing.fake? - end + refute Sidekiq::Testing.enabled? + refute Sidekiq::Testing.inline? + refute Sidekiq::Testing.fake? end end diff --git a/test/test_testing_fake.rb b/test/test_testing_fake.rb index 05fac07df..7b23c09ce 100644 --- a/test/test_testing_fake.rb +++ b/test/test_testing_fake.rb @@ -1,275 +1,273 @@ # frozen_string_literal: true require_relative 'helper' -class TestFake < Minitest::Test - describe 'sidekiq testing' do - class PerformError < RuntimeError; end +describe 'Sidekiq::Testing.fake' do + class PerformError < RuntimeError; end - class DirectWorker - include Sidekiq::Worker - def perform(a, b) - a + b - end - end - - class EnqueuedWorker - include Sidekiq::Worker - def perform(a, b) - a + b - end + class DirectWorker + include Sidekiq::Worker + def perform(a, b) + a + b end + end - class StoredWorker - include Sidekiq::Worker - def perform(error) - raise PerformError if error - end + class EnqueuedWorker + include Sidekiq::Worker + def perform(a, b) + a + b end + end - before do - require 'sidekiq/testing' - Sidekiq::Testing.fake! - EnqueuedWorker.jobs.clear - DirectWorker.jobs.clear + class StoredWorker + include Sidekiq::Worker + def perform(error) + raise PerformError if error end + end - after do - Sidekiq::Testing.disable! - Sidekiq::Queues.clear_all - end + before do + require 'sidekiq/testing' + Sidekiq::Testing.fake! + EnqueuedWorker.jobs.clear + DirectWorker.jobs.clear + end - it 'stubs the async call' do - assert_equal 0, DirectWorker.jobs.size - assert DirectWorker.perform_async(1, 2) - assert_in_delta Time.now.to_f, DirectWorker.jobs.last['enqueued_at'], 0.1 - assert_equal 1, DirectWorker.jobs.size - assert DirectWorker.perform_in(10, 1, 2) - refute DirectWorker.jobs.last['enqueued_at'] - assert_equal 2, DirectWorker.jobs.size - assert DirectWorker.perform_at(10, 1, 2) - assert_equal 3, DirectWorker.jobs.size - assert_in_delta 10.seconds.from_now.to_f, DirectWorker.jobs.last['at'], 0.1 - end + after do + Sidekiq::Testing.disable! + Sidekiq::Queues.clear_all + end - describe 'delayed' do - require 'action_mailer' - class FooMailer < ActionMailer::Base - def bar(str) - str - end - end + it 'stubs the async call' do + assert_equal 0, DirectWorker.jobs.size + assert DirectWorker.perform_async(1, 2) + assert_in_delta Time.now.to_f, DirectWorker.jobs.last['enqueued_at'], 0.1 + assert_equal 1, DirectWorker.jobs.size + assert DirectWorker.perform_in(10, 1, 2) + refute DirectWorker.jobs.last['enqueued_at'] + assert_equal 2, DirectWorker.jobs.size + assert DirectWorker.perform_at(10, 1, 2) + assert_equal 3, DirectWorker.jobs.size + assert_in_delta 10.seconds.from_now.to_f, DirectWorker.jobs.last['at'], 0.1 + end - before do - Sidekiq::Extensions.enable_delay! + describe 'delayed' do + require 'action_mailer' + class FooMailer < ActionMailer::Base + def bar(str) + str end + end - it 'stubs the delay call on mailers' do - assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size - FooMailer.delay.bar('hello!') - assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size - end + before do + Sidekiq::Extensions.enable_delay! + end - class Something - def self.foo(x) - end - end + it 'stubs the delay call on mailers' do + assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size + FooMailer.delay.bar('hello!') + assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size + end - it 'stubs the delay call on classes' do - assert_equal 0, Sidekiq::Extensions::DelayedClass.jobs.size - Something.delay.foo(Date.today) - assert_equal 1, Sidekiq::Extensions::DelayedClass.jobs.size + class Something + def self.foo(x) end end - it 'stubs the enqueue call' do - assert_equal 0, EnqueuedWorker.jobs.size - assert Sidekiq::Client.enqueue(EnqueuedWorker, 1, 2) - assert_equal 1, EnqueuedWorker.jobs.size + it 'stubs the delay call on classes' do + assert_equal 0, Sidekiq::Extensions::DelayedClass.jobs.size + Something.delay.foo(Date.today) + assert_equal 1, Sidekiq::Extensions::DelayedClass.jobs.size end + end - it 'stubs the enqueue_to call' do - assert_equal 0, EnqueuedWorker.jobs.size - assert Sidekiq::Client.enqueue_to('someq', EnqueuedWorker, 1, 2) - assert_equal 1, Sidekiq::Queues['someq'].size - end + it 'stubs the enqueue call' do + assert_equal 0, EnqueuedWorker.jobs.size + assert Sidekiq::Client.enqueue(EnqueuedWorker, 1, 2) + assert_equal 1, EnqueuedWorker.jobs.size + end - it 'executes all stored jobs' do - assert StoredWorker.perform_async(false) - assert StoredWorker.perform_async(true) + it 'stubs the enqueue_to call' do + assert_equal 0, EnqueuedWorker.jobs.size + assert Sidekiq::Client.enqueue_to('someq', EnqueuedWorker, 1, 2) + assert_equal 1, Sidekiq::Queues['someq'].size + end - assert_equal 2, StoredWorker.jobs.size - assert_raises PerformError do - StoredWorker.drain - end - assert_equal 0, StoredWorker.jobs.size + it 'executes all stored jobs' do + assert StoredWorker.perform_async(false) + assert StoredWorker.perform_async(true) + + assert_equal 2, StoredWorker.jobs.size + assert_raises PerformError do + StoredWorker.drain end + assert_equal 0, StoredWorker.jobs.size + end - class SpecificJidWorker - include Sidekiq::Worker - sidekiq_class_attribute :count - self.count = 0 - def perform(worker_jid) - return unless worker_jid == self.jid - self.class.count += 1 - end + class SpecificJidWorker + include Sidekiq::Worker + sidekiq_class_attribute :count + self.count = 0 + def perform(worker_jid) + return unless worker_jid == self.jid + self.class.count += 1 end + end - it 'execute only jobs with assigned JID' do - 4.times do |i| - jid = SpecificJidWorker.perform_async(nil) - if i % 2 == 0 - SpecificJidWorker.jobs[-1]["args"] = ["wrong_jid"] - else - SpecificJidWorker.jobs[-1]["args"] = [jid] - end + it 'execute only jobs with assigned JID' do + 4.times do |i| + jid = SpecificJidWorker.perform_async(nil) + if i % 2 == 0 + SpecificJidWorker.jobs[-1]["args"] = ["wrong_jid"] + else + SpecificJidWorker.jobs[-1]["args"] = [jid] end + end - SpecificJidWorker.perform_one - assert_equal 0, SpecificJidWorker.count + SpecificJidWorker.perform_one + assert_equal 0, SpecificJidWorker.count - SpecificJidWorker.perform_one - assert_equal 1, SpecificJidWorker.count + SpecificJidWorker.perform_one + assert_equal 1, SpecificJidWorker.count - SpecificJidWorker.drain - assert_equal 2, SpecificJidWorker.count - end + SpecificJidWorker.drain + assert_equal 2, SpecificJidWorker.count + end - it 'round trip serializes the job arguments' do - assert StoredWorker.perform_async(:mike) - job = StoredWorker.jobs.first - assert_equal "mike", job['args'].first - StoredWorker.clear - end + it 'round trip serializes the job arguments' do + assert StoredWorker.perform_async(:mike) + job = StoredWorker.jobs.first + assert_equal "mike", job['args'].first + StoredWorker.clear + end - it 'perform_one runs only one job' do - DirectWorker.perform_async(1, 2) - DirectWorker.perform_async(3, 4) - assert_equal 2, DirectWorker.jobs.size + it 'perform_one runs only one job' do + DirectWorker.perform_async(1, 2) + DirectWorker.perform_async(3, 4) + assert_equal 2, DirectWorker.jobs.size - DirectWorker.perform_one - assert_equal 1, DirectWorker.jobs.size + DirectWorker.perform_one + assert_equal 1, DirectWorker.jobs.size - DirectWorker.clear - end + DirectWorker.clear + end - it 'perform_one raise error upon empty queue' do - DirectWorker.clear - assert_raises Sidekiq::EmptyQueueError do - DirectWorker.perform_one - end + it 'perform_one raise error upon empty queue' do + DirectWorker.clear + assert_raises Sidekiq::EmptyQueueError do + DirectWorker.perform_one end + end - class FirstWorker - include Sidekiq::Worker - sidekiq_class_attribute :count - self.count = 0 - def perform - self.class.count += 1 - end + class FirstWorker + include Sidekiq::Worker + sidekiq_class_attribute :count + self.count = 0 + def perform + self.class.count += 1 end + end - class SecondWorker - include Sidekiq::Worker - sidekiq_class_attribute :count - self.count = 0 - def perform - self.class.count += 1 - end + class SecondWorker + include Sidekiq::Worker + sidekiq_class_attribute :count + self.count = 0 + def perform + self.class.count += 1 end + end - class ThirdWorker - include Sidekiq::Worker - sidekiq_class_attribute :count - def perform - FirstWorker.perform_async - SecondWorker.perform_async - end + class ThirdWorker + include Sidekiq::Worker + sidekiq_class_attribute :count + def perform + FirstWorker.perform_async + SecondWorker.perform_async end + end - it 'clears jobs across all workers' do - Sidekiq::Worker.jobs.clear - FirstWorker.count = 0 - SecondWorker.count = 0 + it 'clears jobs across all workers' do + Sidekiq::Worker.jobs.clear + FirstWorker.count = 0 + SecondWorker.count = 0 - assert_equal 0, FirstWorker.jobs.size - assert_equal 0, SecondWorker.jobs.size + assert_equal 0, FirstWorker.jobs.size + assert_equal 0, SecondWorker.jobs.size - FirstWorker.perform_async - SecondWorker.perform_async + FirstWorker.perform_async + SecondWorker.perform_async - assert_equal 1, FirstWorker.jobs.size - assert_equal 1, SecondWorker.jobs.size + assert_equal 1, FirstWorker.jobs.size + assert_equal 1, SecondWorker.jobs.size - Sidekiq::Worker.clear_all + Sidekiq::Worker.clear_all - assert_equal 0, FirstWorker.jobs.size - assert_equal 0, SecondWorker.jobs.size + assert_equal 0, FirstWorker.jobs.size + assert_equal 0, SecondWorker.jobs.size - assert_equal 0, FirstWorker.count - assert_equal 0, SecondWorker.count - end + assert_equal 0, FirstWorker.count + assert_equal 0, SecondWorker.count + end - it 'drains jobs across all workers' do - Sidekiq::Worker.jobs.clear - FirstWorker.count = 0 - SecondWorker.count = 0 + it 'drains jobs across all workers' do + Sidekiq::Worker.jobs.clear + FirstWorker.count = 0 + SecondWorker.count = 0 - assert_equal 0, FirstWorker.jobs.size - assert_equal 0, SecondWorker.jobs.size + assert_equal 0, FirstWorker.jobs.size + assert_equal 0, SecondWorker.jobs.size - assert_equal 0, FirstWorker.count - assert_equal 0, SecondWorker.count + assert_equal 0, FirstWorker.count + assert_equal 0, SecondWorker.count - FirstWorker.perform_async - SecondWorker.perform_async + FirstWorker.perform_async + SecondWorker.perform_async - assert_equal 1, FirstWorker.jobs.size - assert_equal 1, SecondWorker.jobs.size + assert_equal 1, FirstWorker.jobs.size + assert_equal 1, SecondWorker.jobs.size - Sidekiq::Worker.drain_all + Sidekiq::Worker.drain_all - assert_equal 0, FirstWorker.jobs.size - assert_equal 0, SecondWorker.jobs.size + assert_equal 0, FirstWorker.jobs.size + assert_equal 0, SecondWorker.jobs.size - assert_equal 1, FirstWorker.count - assert_equal 1, SecondWorker.count - end + assert_equal 1, FirstWorker.count + assert_equal 1, SecondWorker.count + end - it 'drains jobs across all workers even when workers create new jobs' do - Sidekiq::Worker.jobs.clear - FirstWorker.count = 0 - SecondWorker.count = 0 + it 'drains jobs across all workers even when workers create new jobs' do + Sidekiq::Worker.jobs.clear + FirstWorker.count = 0 + SecondWorker.count = 0 - assert_equal 0, ThirdWorker.jobs.size + assert_equal 0, ThirdWorker.jobs.size - assert_equal 0, FirstWorker.count - assert_equal 0, SecondWorker.count + assert_equal 0, FirstWorker.count + assert_equal 0, SecondWorker.count - ThirdWorker.perform_async + ThirdWorker.perform_async - assert_equal 1, ThirdWorker.jobs.size + assert_equal 1, ThirdWorker.jobs.size - Sidekiq::Worker.drain_all + Sidekiq::Worker.drain_all - assert_equal 0, ThirdWorker.jobs.size + assert_equal 0, ThirdWorker.jobs.size - assert_equal 1, FirstWorker.count - assert_equal 1, SecondWorker.count - end + assert_equal 1, FirstWorker.count + assert_equal 1, SecondWorker.count + end - it 'drains jobs of workers with symbolized queue names' do - Sidekiq::Worker.jobs.clear + it 'drains jobs of workers with symbolized queue names' do + Sidekiq::Worker.jobs.clear - AltQueueWorker.perform_async(5,6) - assert_equal 1, AltQueueWorker.jobs.size + AltQueueWorker.perform_async(5,6) + assert_equal 1, AltQueueWorker.jobs.size - Sidekiq::Worker.drain_all - assert_equal 0, AltQueueWorker.jobs.size - end + Sidekiq::Worker.drain_all + assert_equal 0, AltQueueWorker.jobs.size + end - it 'can execute a job' do - DirectWorker.execute_job(DirectWorker.new, [2, 3]) - end + it 'can execute a job' do + DirectWorker.execute_job(DirectWorker.new, [2, 3]) end describe 'queue testing' do diff --git a/test/test_testing_inline.rb b/test/test_testing_inline.rb index bfa6c9495..f3cb4b118 100644 --- a/test/test_testing_inline.rb +++ b/test/test_testing_inline.rb @@ -1,93 +1,91 @@ # frozen_string_literal: true require_relative 'helper' -class TestInline < Minitest::Test - describe 'sidekiq inline testing' do - class InlineError < RuntimeError; end - class ParameterIsNotString < RuntimeError; end - - class InlineWorker - include Sidekiq::Worker - def perform(pass) - raise ArgumentError, "no jid" unless jid - raise InlineError unless pass - end +describe 'Sidekiq::Testing.inline' do + class InlineError < RuntimeError; end + class ParameterIsNotString < RuntimeError; end + + class InlineWorker + include Sidekiq::Worker + def perform(pass) + raise ArgumentError, "no jid" unless jid + raise InlineError unless pass end + end - class InlineWorkerWithTimeParam - include Sidekiq::Worker - def perform(time) - raise ParameterIsNotString unless time.is_a?(String) || time.is_a?(Numeric) - end + class InlineWorkerWithTimeParam + include Sidekiq::Worker + def perform(time) + raise ParameterIsNotString unless time.is_a?(String) || time.is_a?(Numeric) end + end - before do - require 'sidekiq/testing/inline' - Sidekiq::Testing.inline! - end + before do + require 'sidekiq/testing/inline' + Sidekiq::Testing.inline! + end - after do - Sidekiq::Testing.disable! - end + after do + Sidekiq::Testing.disable! + end - it 'stubs the async call when in testing mode' do - assert InlineWorker.perform_async(true) + it 'stubs the async call when in testing mode' do + assert InlineWorker.perform_async(true) - assert_raises InlineError do - InlineWorker.perform_async(false) - end + assert_raises InlineError do + InlineWorker.perform_async(false) end + end - describe 'delay' do - require 'action_mailer' - class InlineFooMailer < ActionMailer::Base - def bar(str) - raise InlineError - end - end - - class InlineFooModel - def self.bar(str) - raise InlineError - end + describe 'delay' do + require 'action_mailer' + class InlineFooMailer < ActionMailer::Base + def bar(str) + raise InlineError end + end - before do - Sidekiq::Extensions.enable_delay! + class InlineFooModel + def self.bar(str) + raise InlineError end + end - it 'stubs the delay call on mailers' do - assert_raises InlineError do - InlineFooMailer.delay.bar('three') - end - end + before do + Sidekiq::Extensions.enable_delay! + end - it 'stubs the delay call on models' do - assert_raises InlineError do - InlineFooModel.delay.bar('three') - end + it 'stubs the delay call on mailers' do + assert_raises InlineError do + InlineFooMailer.delay.bar('three') end end - it 'stubs the enqueue call when in testing mode' do - assert Sidekiq::Client.enqueue(InlineWorker, true) - + it 'stubs the delay call on models' do assert_raises InlineError do - Sidekiq::Client.enqueue(InlineWorker, false) + InlineFooModel.delay.bar('three') end end + end - it 'stubs the push_bulk call when in testing mode' do - assert Sidekiq::Client.push_bulk({'class' => InlineWorker, 'args' => [[true], [true]]}) + it 'stubs the enqueue call when in testing mode' do + assert Sidekiq::Client.enqueue(InlineWorker, true) - assert_raises InlineError do - Sidekiq::Client.push_bulk({'class' => InlineWorker, 'args' => [[true], [false]]}) - end + assert_raises InlineError do + Sidekiq::Client.enqueue(InlineWorker, false) end + end - it 'should relay parameters through json' do - assert Sidekiq::Client.enqueue(InlineWorkerWithTimeParam, Time.now.to_f) + it 'stubs the push_bulk call when in testing mode' do + assert Sidekiq::Client.push_bulk({'class' => InlineWorker, 'args' => [[true], [true]]}) + + assert_raises InlineError do + Sidekiq::Client.push_bulk({'class' => InlineWorker, 'args' => [[true], [false]]}) end + end + it 'should relay parameters through json' do + assert Sidekiq::Client.enqueue(InlineWorkerWithTimeParam, Time.now.to_f) end + end diff --git a/test/test_web.rb b/test/test_web.rb index b7e035f17..0716d2571 100644 --- a/test/test_web.rb +++ b/test/test_web.rb @@ -5,676 +5,675 @@ require 'sidekiq/util' require 'rack/test' -class TestWeb < Minitest::Test - describe 'sidekiq web' do - include Rack::Test::Methods +describe Sidekiq::Web do + include Rack::Test::Methods - def app - Sidekiq::Web - end + def app + Sidekiq::Web + end - def job_params(job, score) - "#{score}-#{job['jid']}" - end + def job_params(job, score) + "#{score}-#{job['jid']}" + end - before do - Sidekiq.redis {|c| c.flushdb } - end + before do + Sidekiq.redis {|c| c.flushdb } + end - class WebWorker - include Sidekiq::Worker + class WebWorker + include Sidekiq::Worker - def perform(a, b) - a + b - end + def perform(a, b) + a + b end + end - it 'can configure via set() syntax' do - app.set(:session_secret, "foo") - assert_equal "foo", app.session_secret - end - - it 'can show text with any locales' do - rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'ru,en'} - get '/', {}, rackenv - assert_match(/Панель управления/, last_response.body) - rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'es,en'} - get '/', {}, rackenv - assert_match(/Panel de Control/, last_response.body) - rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'en-us'} - get '/', {}, rackenv - assert_match(/Dashboard/, last_response.body) - rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'zh-cn'} - get '/', {}, rackenv - assert_match(/信息板/, last_response.body) - rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'zh-tw'} - get '/', {}, rackenv - assert_match(/資訊主頁/, last_response.body) - rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'nb'} - get '/', {}, rackenv - assert_match(/Oversikt/, last_response.body) - end - - it 'can provide a default, appropriate CSP for its content' do - get '/', {} - policies = last_response.headers["Content-Security-Policy"].split('; ') - assert_includes(policies, "connect-src 'self' https: http: wss: ws:") - assert_includes(policies, "style-src 'self' https: http: 'unsafe-inline'") - assert_includes(policies, "script-src 'self' https: http: 'unsafe-inline'") - assert_includes(policies, "object-src 'none'") - end - - describe 'busy' do - - it 'can display workers' do - Sidekiq.redis do |conn| - conn.incr('busy') - conn.sadd('processes', 'foo:1234') - conn.hmset('foo:1234', 'info', Sidekiq.dump_json('hostname' => 'foo', 'started_at' => Time.now.to_f, "queues" => []), 'at', Time.now.to_f, 'busy', 4) - identity = 'foo:1234:workers' - hash = {:queue => 'critical', :payload => { 'class' => WebWorker.name, 'args' => [1,'abc'] }, :run_at => Time.now.to_i } - conn.hmset(identity, 1001, Sidekiq.dump_json(hash)) - end - assert_equal ['1001'], Sidekiq::Workers.new.map { |pid, tid, data| tid } - - get '/busy' - assert_equal 200, last_response.status - assert_match(/status-active/, last_response.body) - assert_match(/critical/, last_response.body) - assert_match(/WebWorker/, last_response.body) - end + it 'can configure via set() syntax' do + app.set(:session_secret, "foo") + assert_equal "foo", app.session_secret + end - it 'can quiet a process' do - identity = 'identity' - signals_key = "#{identity}-signals" + it 'can show text with any locales' do + rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'ru,en'} + get '/', {}, rackenv + assert_match(/Панель управления/, last_response.body) + rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'es,en'} + get '/', {}, rackenv + assert_match(/Panel de Control/, last_response.body) + rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'en-us'} + get '/', {}, rackenv + assert_match(/Dashboard/, last_response.body) + rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'zh-cn'} + get '/', {}, rackenv + assert_match(/信息板/, last_response.body) + rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'zh-tw'} + get '/', {}, rackenv + assert_match(/資訊主頁/, last_response.body) + rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'nb'} + get '/', {}, rackenv + assert_match(/Oversikt/, last_response.body) + end - assert_nil Sidekiq.redis { |c| c.lpop signals_key } - post '/busy', 'quiet' => '1', 'identity' => identity - assert_equal 302, last_response.status - assert_equal 'TSTP', Sidekiq.redis { |c| c.lpop signals_key } - end + it 'can provide a default, appropriate CSP for its content' do + get '/', {} + policies = last_response.headers["Content-Security-Policy"].split('; ') + assert_includes(policies, "connect-src 'self' https: http: wss: ws:") + assert_includes(policies, "style-src 'self' https: http: 'unsafe-inline'") + assert_includes(policies, "script-src 'self' https: http: 'unsafe-inline'") + assert_includes(policies, "object-src 'none'") + end - it 'can stop a process' do - identity = 'identity' - signals_key = "#{identity}-signals" + describe 'busy' do - assert_nil Sidekiq.redis { |c| c.lpop signals_key } - post '/busy', 'stop' => '1', 'identity' => identity - assert_equal 302, last_response.status - assert_equal 'TERM', Sidekiq.redis { |c| c.lpop signals_key } + it 'can display workers' do + Sidekiq.redis do |conn| + conn.incr('busy') + conn.sadd('processes', 'foo:1234') + conn.hmset('foo:1234', 'info', Sidekiq.dump_json('hostname' => 'foo', 'started_at' => Time.now.to_f, "queues" => []), 'at', Time.now.to_f, 'busy', 4) + identity = 'foo:1234:workers' + hash = {:queue => 'critical', :payload => { 'class' => WebWorker.name, 'args' => [1,'abc'] }, :run_at => Time.now.to_i } + conn.hmset(identity, 1001, Sidekiq.dump_json(hash)) end - end + assert_equal ['1001'], Sidekiq::Workers.new.map { |pid, tid, data| tid } - it 'can display queues' do - assert Sidekiq::Client.push('queue' => :foo, 'class' => WebWorker, 'args' => [1, 3]) - - get '/queues' + get '/busy' assert_equal 200, last_response.status - assert_match(/foo/, last_response.body) - refute_match(/HardWorker/, last_response.body) + assert_match(/status-active/, last_response.body) + assert_match(/critical/, last_response.body) + assert_match(/WebWorker/, last_response.body) end - it 'handles queue view' do - get '/queues/default' - assert_equal 200, last_response.status + it 'can quiet a process' do + identity = 'identity' + signals_key = "#{identity}-signals" + + assert_nil Sidekiq.redis { |c| c.lpop signals_key } + post '/busy', 'quiet' => '1', 'identity' => identity + assert_equal 302, last_response.status + assert_equal 'TSTP', Sidekiq.redis { |c| c.lpop signals_key } end - it 'can delete a queue' do - Sidekiq.redis do |conn| - conn.rpush('queue:foo', '{\"args\":[]}') - conn.sadd('queues', 'foo') - end + it 'can stop a process' do + identity = 'identity' + signals_key = "#{identity}-signals" - get '/queues/foo' - assert_equal 200, last_response.status - - post '/queues/foo' + assert_nil Sidekiq.redis { |c| c.lpop signals_key } + post '/busy', 'stop' => '1', 'identity' => identity assert_equal 302, last_response.status - - Sidekiq.redis do |conn| - refute conn.smembers('queues').include?('foo') - refute conn.exists('queue:foo') - end + assert_equal 'TERM', Sidekiq.redis { |c| c.lpop signals_key } end + end - it 'can delete a job' do - Sidekiq.redis do |conn| - conn.rpush('queue:foo', "{\"args\":[]}") - conn.rpush('queue:foo', "{\"foo\":\"bar\",\"args\":[]}") - conn.rpush('queue:foo', "{\"foo2\":\"bar2\",\"args\":[]}") - end + it 'can display queues' do + assert Sidekiq::Client.push('queue' => :foo, 'class' => WebWorker, 'args' => [1, 3], 'enqueued_at' => Time.now.to_f - 65) - get '/queues/foo' - assert_equal 200, last_response.status + get '/queues' + assert_equal 200, last_response.status + assert_match(/foo/, last_response.body) + refute_match(/HardWorker/, last_response.body) + assert_match(/seconds ago/, last_response.body) + end - post '/queues/foo/delete', key_val: "{\"foo\":\"bar\"}" - assert_equal 302, last_response.status + it 'handles queue view' do + get '/queues/default' + assert_equal 200, last_response.status + end - Sidekiq.redis do |conn| - refute conn.lrange('queue:foo', 0, -1).include?("{\"foo\":\"bar\"}") - end + it 'can delete a queue' do + Sidekiq.redis do |conn| + conn.rpush('queue:foo', '{\"args\":[]}') + conn.sadd('queues', 'foo') end - it 'can display retries' do - get '/retries' - assert_equal 200, last_response.status - assert_match(/found/, last_response.body) - refute_match(/HardWorker/, last_response.body) + get '/queues/foo' + assert_equal 200, last_response.status - add_retry + post '/queues/foo' + assert_equal 302, last_response.status - get '/retries' - assert_equal 200, last_response.status - refute_match(/found/, last_response.body) - assert_match(/HardWorker/, last_response.body) + Sidekiq.redis do |conn| + refute conn.smembers('queues').include?('foo') + refute conn.exists('queue:foo') end + end - it 'can display a single retry' do - params = add_retry - get '/retries/0-shouldntexist' - assert_equal 302, last_response.status - get "/retries/#{job_params(*params)}" - assert_equal 200, last_response.status - assert_match(/HardWorker/, last_response.body) + it 'can delete a job' do + Sidekiq.redis do |conn| + conn.rpush('queue:foo', "{\"args\":[]}") + conn.rpush('queue:foo', "{\"foo\":\"bar\",\"args\":[]}") + conn.rpush('queue:foo', "{\"foo2\":\"bar2\",\"args\":[]}") end - it 'handles missing retry' do - get "/retries/0-shouldntexist" - assert_equal 302, last_response.status - end + get '/queues/foo' + assert_equal 200, last_response.status - it 'can delete a single retry' do - params = add_retry - post "/retries/#{job_params(*params)}", 'delete' => 'Delete' - assert_equal 302, last_response.status - assert_equal 'http://example.org/retries', last_response.header['Location'] + post '/queues/foo/delete', key_val: "{\"foo\":\"bar\"}" + assert_equal 302, last_response.status - get "/retries" - assert_equal 200, last_response.status - refute_match(/#{params.first['args'][2]}/, last_response.body) + Sidekiq.redis do |conn| + refute conn.lrange('queue:foo', 0, -1).include?("{\"foo\":\"bar\"}") end + end - it 'can delete all retries' do - 3.times { add_retry } + it 'can display retries' do + get '/retries' + assert_equal 200, last_response.status + assert_match(/found/, last_response.body) + refute_match(/HardWorker/, last_response.body) - post "/retries/all/delete", 'delete' => 'Delete' - assert_equal 0, Sidekiq::RetrySet.new.size - assert_equal 302, last_response.status - assert_equal 'http://example.org/retries', last_response.header['Location'] - end + add_retry - it 'can retry a single retry now' do - params = add_retry - post "/retries/#{job_params(*params)}", 'retry' => 'Retry' - assert_equal 302, last_response.status - assert_equal 'http://example.org/retries', last_response.header['Location'] + get '/retries' + assert_equal 200, last_response.status + refute_match(/found/, last_response.body) + assert_match(/HardWorker/, last_response.body) + end - get '/queues/default' - assert_equal 200, last_response.status - assert_match(/#{params.first['args'][2]}/, last_response.body) - end + it 'can display a single retry' do + params = add_retry + get '/retries/0-shouldntexist' + assert_equal 302, last_response.status + get "/retries/#{job_params(*params)}" + assert_equal 200, last_response.status + assert_match(/HardWorker/, last_response.body) + end - it 'can kill a single retry now' do - params = add_retry - post "/retries/#{job_params(*params)}", 'kill' => 'Kill' - assert_equal 302, last_response.status - assert_equal 'http://example.org/retries', last_response.header['Location'] + it 'handles missing retry' do + get "/retries/0-shouldntexist" + assert_equal 302, last_response.status + end - get '/morgue' - assert_equal 200, last_response.status - assert_match(/#{params.first['args'][2]}/, last_response.body) - end + it 'can delete a single retry' do + params = add_retry + post "/retries/#{job_params(*params)}", 'delete' => 'Delete' + assert_equal 302, last_response.status + assert_equal 'http://example.org/retries', last_response.header['Location'] - it 'can display scheduled' do - get '/scheduled' - assert_equal 200, last_response.status - assert_match(/found/, last_response.body) - refute_match(/HardWorker/, last_response.body) + get "/retries" + assert_equal 200, last_response.status + refute_match(/#{params.first['args'][2]}/, last_response.body) + end - add_scheduled + it 'can delete all retries' do + 3.times { add_retry } - get '/scheduled' - assert_equal 200, last_response.status - refute_match(/found/, last_response.body) - assert_match(/HardWorker/, last_response.body) - end + post "/retries/all/delete", 'delete' => 'Delete' + assert_equal 0, Sidekiq::RetrySet.new.size + assert_equal 302, last_response.status + assert_equal 'http://example.org/retries', last_response.header['Location'] + end - it 'can display a single scheduled job' do - params = add_scheduled - get '/scheduled/0-shouldntexist' - assert_equal 302, last_response.status - get "/scheduled/#{job_params(*params)}" - assert_equal 200, last_response.status - assert_match(/HardWorker/, last_response.body) - end + it 'can retry a single retry now' do + params = add_retry + post "/retries/#{job_params(*params)}", 'retry' => 'Retry' + assert_equal 302, last_response.status + assert_equal 'http://example.org/retries', last_response.header['Location'] - it 'handles missing scheduled job' do - get "/scheduled/0-shouldntexist" - assert_equal 302, last_response.status - end + get '/queues/default' + assert_equal 200, last_response.status + assert_match(/#{params.first['args'][2]}/, last_response.body) + end - it 'can add to queue a single scheduled job' do - params = add_scheduled - post "/scheduled/#{job_params(*params)}", 'add_to_queue' => true - assert_equal 302, last_response.status - assert_equal 'http://example.org/scheduled', last_response.header['Location'] + it 'can kill a single retry now' do + params = add_retry + post "/retries/#{job_params(*params)}", 'kill' => 'Kill' + assert_equal 302, last_response.status + assert_equal 'http://example.org/retries', last_response.header['Location'] - get '/queues/default' - assert_equal 200, last_response.status - assert_match(/#{params.first['args'][2]}/, last_response.body) - end + get '/morgue' + assert_equal 200, last_response.status + assert_match(/#{params.first['args'][2]}/, last_response.body) + end - it 'can delete a single scheduled job' do - params = add_scheduled - post "/scheduled/#{job_params(*params)}", 'delete' => 'Delete' - assert_equal 302, last_response.status - assert_equal 'http://example.org/scheduled', last_response.header['Location'] + it 'can display scheduled' do + get '/scheduled' + assert_equal 200, last_response.status + assert_match(/found/, last_response.body) + refute_match(/HardWorker/, last_response.body) - get "/scheduled" - assert_equal 200, last_response.status - refute_match(/#{params.first['args'][2]}/, last_response.body) - end + add_scheduled - it 'can delete scheduled' do - params = add_scheduled - Sidekiq.redis do |conn| - assert_equal 1, conn.zcard('schedule') - post '/scheduled', 'key' => [job_params(*params)], 'delete' => 'Delete' - assert_equal 302, last_response.status - assert_equal 'http://example.org/scheduled', last_response.header['Location'] - assert_equal 0, conn.zcard('schedule') - end - end + get '/scheduled' + assert_equal 200, last_response.status + refute_match(/found/, last_response.body) + assert_match(/HardWorker/, last_response.body) + end - it "can move scheduled to default queue" do - q = Sidekiq::Queue.new - params = add_scheduled - Sidekiq.redis do |conn| - assert_equal 1, conn.zcard('schedule') - assert_equal 0, q.size - post '/scheduled', 'key' => [job_params(*params)], 'add_to_queue' => 'AddToQueue' - assert_equal 302, last_response.status - assert_equal 'http://example.org/scheduled', last_response.header['Location'] - assert_equal 0, conn.zcard('schedule') - assert_equal 1, q.size - get '/queues/default' - assert_equal 200, last_response.status - assert_match(/#{params[0]['args'][2]}/, last_response.body) - end - end + it 'can display a single scheduled job' do + params = add_scheduled + get '/scheduled/0-shouldntexist' + assert_equal 302, last_response.status + get "/scheduled/#{job_params(*params)}" + assert_equal 200, last_response.status + assert_match(/HardWorker/, last_response.body) + end + + it 'handles missing scheduled job' do + get "/scheduled/0-shouldntexist" + assert_equal 302, last_response.status + end - it 'can retry all retries' do - msg = add_retry.first - add_retry + it 'can add to queue a single scheduled job' do + params = add_scheduled + post "/scheduled/#{job_params(*params)}", 'add_to_queue' => true + assert_equal 302, last_response.status + assert_equal 'http://example.org/scheduled', last_response.header['Location'] - post "/retries/all/retry", 'retry' => 'Retry' + get '/queues/default' + assert_equal 200, last_response.status + assert_match(/#{params.first['args'][2]}/, last_response.body) + end + + it 'can delete a single scheduled job' do + params = add_scheduled + post "/scheduled/#{job_params(*params)}", 'delete' => 'Delete' + assert_equal 302, last_response.status + assert_equal 'http://example.org/scheduled', last_response.header['Location'] + + get "/scheduled" + assert_equal 200, last_response.status + refute_match(/#{params.first['args'][2]}/, last_response.body) + end + + it 'can delete scheduled' do + params = add_scheduled + Sidekiq.redis do |conn| + assert_equal 1, conn.zcard('schedule') + post '/scheduled', 'key' => [job_params(*params)], 'delete' => 'Delete' assert_equal 302, last_response.status - assert_equal 'http://example.org/retries', last_response.header['Location'] - assert_equal 2, Sidekiq::Queue.new("default").size + assert_equal 'http://example.org/scheduled', last_response.header['Location'] + assert_equal 0, conn.zcard('schedule') + end + end + it "can move scheduled to default queue" do + q = Sidekiq::Queue.new + params = add_scheduled + Sidekiq.redis do |conn| + assert_equal 1, conn.zcard('schedule') + assert_equal 0, q.size + post '/scheduled', 'key' => [job_params(*params)], 'add_to_queue' => 'AddToQueue' + assert_equal 302, last_response.status + assert_equal 'http://example.org/scheduled', last_response.header['Location'] + assert_equal 0, conn.zcard('schedule') + assert_equal 1, q.size get '/queues/default' assert_equal 200, last_response.status - assert_match(/#{msg['args'][2]}/, last_response.body) + assert_match(/#{params[0]['args'][2]}/, last_response.body) end + end - it 'calls updatePage() once when polling' do - get '/busy?poll=true' - assert_equal 200, last_response.status - assert_equal 1, last_response.body.scan('data-poll-path="/busy').count - end + it 'can retry all retries' do + msg = add_retry.first + add_retry - it 'escape job args and error messages' do - # on /retries page - params = add_xss_retry - get '/retries' - assert_equal 200, last_response.status - assert_match(/FailWorker/, last_response.body) + post "/retries/all/retry", 'retry' => 'Retry' + assert_equal 302, last_response.status + assert_equal 'http://example.org/retries', last_response.header['Location'] + assert_equal 2, Sidekiq::Queue.new("default").size - assert last_response.body.include?( "fail message: <a>hello</a>" ) - assert !last_response.body.include?( "fail message: hello" ) + get '/queues/default' + assert_equal 200, last_response.status + assert_match(/#{msg['args'][2]}/, last_response.body) + end - assert last_response.body.include?( "args\">"<a>hello</a>"<" ) - assert !last_response.body.include?( "args\">hello<" ) + it 'calls updatePage() once when polling' do + get '/busy?poll=true' + assert_equal 200, last_response.status + assert_equal 1, last_response.body.scan('data-poll-path="/busy').count + end - # on /workers page - Sidekiq.redis do |conn| - pro = 'foo:1234' - conn.sadd('processes', pro) - conn.hmset(pro, 'info', Sidekiq.dump_json('started_at' => Time.now.to_f, 'labels' => ['frumduz'], 'queues' =>[]), 'busy', 1, 'beat', Time.now.to_f) - identity = "#{pro}:workers" - hash = {:queue => 'critical', :payload => { 'class' => "FailWorker", 'args' => ["hello"] }, :run_at => Time.now.to_i } - conn.hmset(identity, 100001, Sidekiq.dump_json(hash)) - conn.incr('busy') - end + it 'escape job args and error messages' do + # on /retries page + params = add_xss_retry + get '/retries' + assert_equal 200, last_response.status + assert_match(/FailWorker/, last_response.body) + + assert last_response.body.include?( "fail message: <a>hello</a>" ) + assert !last_response.body.include?( "fail message: hello" ) + + assert last_response.body.include?( "args\">"<a>hello</a>"<" ) + assert !last_response.body.include?( "args\">hello<" ) + + # on /workers page + Sidekiq.redis do |conn| + pro = 'foo:1234' + conn.sadd('processes', pro) + conn.hmset(pro, 'info', Sidekiq.dump_json('started_at' => Time.now.to_f, 'labels' => ['frumduz'], 'queues' =>[]), 'busy', 1, 'beat', Time.now.to_f) + identity = "#{pro}:workers" + hash = {:queue => 'critical', :payload => { 'class' => "FailWorker", 'args' => ["hello"] }, :run_at => Time.now.to_i } + conn.hmset(identity, 100001, Sidekiq.dump_json(hash)) + conn.incr('busy') + end + + get '/busy' + assert_equal 200, last_response.status + assert_match(/FailWorker/, last_response.body) + assert_match(/frumduz/, last_response.body) + assert last_response.body.include?( "<a>hello</a>" ) + assert !last_response.body.include?( "hello" ) + + # on /queues page + params = add_xss_retry # sorry, don't know how to easily make this show up on queues page otherwise. + post "/retries/#{job_params(*params)}", 'retry' => 'Retry' + assert_equal 302, last_response.status + + get '/queues/foo' + assert_equal 200, last_response.status + assert last_response.body.include?( "<a>hello</a>" ) + assert !last_response.body.include?( "hello" ) + end - get '/busy' - assert_equal 200, last_response.status - assert_match(/FailWorker/, last_response.body) - assert_match(/frumduz/, last_response.body) - assert last_response.body.include?( "<a>hello</a>" ) - assert !last_response.body.include?( "hello" ) - - # on /queues page - params = add_xss_retry # sorry, don't know how to easily make this show up on queues page otherwise. - post "/retries/#{job_params(*params)}", 'retry' => 'Retry' - assert_equal 302, last_response.status + it 'can show user defined tab' do + begin + Sidekiq::Web.tabs['Custom Tab'] = '/custom' - get '/queues/foo' - assert_equal 200, last_response.status - assert last_response.body.include?( "<a>hello</a>" ) - assert !last_response.body.include?( "hello" ) - end + get '/' + assert_match 'Custom Tab', last_response.body - it 'can show user defined tab' do - begin - Sidekiq::Web.tabs['Custom Tab'] = '/custom' + ensure + Sidekiq::Web.tabs.delete 'Custom Tab' + end + end - get '/' - assert_match 'Custom Tab', last_response.body + it 'can display home' do + get '/' + assert_equal 200, last_response.status + end - ensure - Sidekiq::Web.tabs.delete 'Custom Tab' + describe 'custom locales' do + before do + Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "fixtures") + Sidekiq::Web.tabs['Custom Tab'] = '/custom' + Sidekiq::WebApplication.get('/custom') do + clear_caches # ugly hack since I can't figure out how to access WebHelpers outside of this context + t('translated_text') end end - it 'can display home' do - get '/' - assert_equal 200, last_response.status + after do + Sidekiq::Web.tabs.delete 'Custom Tab' + Sidekiq::Web.settings.locales.pop end - describe 'custom locales' do - before do - Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "fixtures") - Sidekiq::Web.tabs['Custom Tab'] = '/custom' - Sidekiq::WebApplication.get('/custom') do - clear_caches # ugly hack since I can't figure out how to access WebHelpers outside of this context - t('translated_text') - end - end - - after do - Sidekiq::Web.tabs.delete 'Custom Tab' - Sidekiq::Web.settings.locales.pop - end - - it 'can show user defined tab with custom locales' do - get '/custom' + it 'can show user defined tab with custom locales' do + get '/custom' - assert_match(/Changed text/, last_response.body) - end + assert_match(/Changed text/, last_response.body) end + end - describe 'dashboard/stats' do - it 'redirects to stats' do - get '/dashboard/stats' - assert_equal 302, last_response.status - assert_equal 'http://example.org/stats', last_response.header['Location'] - end + describe 'dashboard/stats' do + it 'redirects to stats' do + get '/dashboard/stats' + assert_equal 302, last_response.status + assert_equal 'http://example.org/stats', last_response.header['Location'] end + end - describe 'stats' do - include Sidekiq::Util + describe 'stats' do + include Sidekiq::Util - before do - Sidekiq.redis do |conn| - conn.set("stat:processed", 5) - conn.set("stat:failed", 2) - conn.sadd("queues", "default") - end - 2.times { add_retry } - 3.times { add_scheduled } - 4.times { add_worker } + before do + Sidekiq.redis do |conn| + conn.set("stat:processed", 5) + conn.set("stat:failed", 2) + conn.sadd("queues", "default") end + 2.times { add_retry } + 3.times { add_scheduled } + 4.times { add_worker } + end - it 'works' do - get '/stats' - @response = Sidekiq.load_json(last_response.body) - - assert_equal 200, last_response.status - assert_includes @response.keys, "sidekiq" - assert_equal 5, @response["sidekiq"]["processed"] - assert_equal 2, @response["sidekiq"]["failed"] - assert_equal 4, @response["sidekiq"]["busy"] - assert_equal 1, @response["sidekiq"]["processes"] - assert_equal 2, @response["sidekiq"]["retries"] - assert_equal 3, @response["sidekiq"]["scheduled"] - assert_equal 0, @response["sidekiq"]["default_latency"] - assert_includes @response.keys, "redis" - assert_includes @response["redis"].keys, "redis_version" - assert_includes @response["redis"].keys, "uptime_in_days" - assert_includes @response["redis"].keys, "connected_clients" - assert_includes @response["redis"].keys, "used_memory_human" - assert_includes @response["redis"].keys, "used_memory_peak_human" - assert_includes @response.keys, "server_utc_time" - end + it 'works' do + get '/stats' + @response = Sidekiq.load_json(last_response.body) + + assert_equal 200, last_response.status + assert_includes @response.keys, "sidekiq" + assert_equal 5, @response["sidekiq"]["processed"] + assert_equal 2, @response["sidekiq"]["failed"] + assert_equal 4, @response["sidekiq"]["busy"] + assert_equal 1, @response["sidekiq"]["processes"] + assert_equal 2, @response["sidekiq"]["retries"] + assert_equal 3, @response["sidekiq"]["scheduled"] + assert_equal 0, @response["sidekiq"]["default_latency"] + assert_includes @response.keys, "redis" + assert_includes @response["redis"].keys, "redis_version" + assert_includes @response["redis"].keys, "uptime_in_days" + assert_includes @response["redis"].keys, "connected_clients" + assert_includes @response["redis"].keys, "used_memory_human" + assert_includes @response["redis"].keys, "used_memory_peak_human" + assert_includes @response.keys, "server_utc_time" end + end - describe 'bad JSON' do - it 'displays without error' do - s = Sidekiq::DeadSet.new - (_, score) = kill_bad - assert_equal 1, s.size + describe 'bad JSON' do + it 'displays without error' do + s = Sidekiq::DeadSet.new + (_, score) = kill_bad + assert_equal 1, s.size - get '/morgue' - assert_equal 200, last_response.status - assert_match(/#{score.to_i}/, last_response.body) - assert_match("something bad", last_response.body) - assert_equal 1, s.size + get '/morgue' + assert_equal 200, last_response.status + assert_match(/#{score.to_i}/, last_response.body) + assert_match("something bad", last_response.body) + assert_equal 1, s.size - post "/morgue/#{score}-", 'delete' => 'Delete' - assert_equal 302, last_response.status - assert_equal 1, s.size - end + post "/morgue/#{score}-", 'delete' => 'Delete' + assert_equal 302, last_response.status + assert_equal 1, s.size end + end - describe 'stats/queues' do - include Sidekiq::Util - - before do - Sidekiq.redis do |conn| - conn.set("stat:processed", 5) - conn.set("stat:failed", 2) - conn.sadd("queues", "default") - conn.sadd("queues", "queue2") - end - 2.times { add_retry } - 3.times { add_scheduled } - 4.times { add_worker } + describe 'stats/queues' do + include Sidekiq::Util - get '/stats/queues' - @response = Sidekiq.load_json(last_response.body) + before do + Sidekiq.redis do |conn| + conn.set("stat:processed", 5) + conn.set("stat:failed", 2) + conn.sadd("queues", "default") + conn.sadd("queues", "queue2") end + 2.times { add_retry } + 3.times { add_scheduled } + 4.times { add_worker } - it 'reports the queue depth' do - assert_equal 0, @response["default"] - assert_equal 0, @response["queue2"] - end + get '/stats/queues' + @response = Sidekiq.load_json(last_response.body) end - describe 'dead jobs' do - it 'shows empty index' do - get 'morgue' - assert_equal 200, last_response.status - end - - it 'shows index with jobs' do - (_, score) = add_dead - get 'morgue' - assert_equal 200, last_response.status - assert_match(/#{score}/, last_response.body) - end + it 'reports the queue depth' do + assert_equal 0, @response["default"] + assert_equal 0, @response["queue2"] + end + end - it 'can delete all dead' do - 3.times { add_dead } + describe 'dead jobs' do + it 'shows empty index' do + get 'morgue' + assert_equal 200, last_response.status + end - assert_equal 3, Sidekiq::DeadSet.new.size - post "/morgue/all/delete", 'delete' => 'Delete' - assert_equal 0, Sidekiq::DeadSet.new.size - assert_equal 302, last_response.status - assert_equal 'http://example.org/morgue', last_response.header['Location'] - end + it 'shows index with jobs' do + (_, score) = add_dead + get 'morgue' + assert_equal 200, last_response.status + assert_match(/#{score}/, last_response.body) + end - it 'can display a dead job' do - params = add_dead - get "/morgue/#{job_params(*params)}" - assert_equal 200, last_response.status - end + it 'can delete all dead' do + 3.times { add_dead } - it 'can retry a dead job' do - params = add_dead - post "/morgue/#{job_params(*params)}", 'retry' => 'Retry' - assert_equal 302, last_response.status - assert_equal 'http://example.org/morgue', last_response.header['Location'] - assert_equal 0, Sidekiq::DeadSet.new.size - - params = add_dead('jid-with-hyphen') - post "/morgue/#{job_params(*params)}", 'retry' => 'Retry' - assert_equal 302, last_response.status - assert_equal 0, Sidekiq::DeadSet.new.size - - get '/queues/foo' - assert_equal 200, last_response.status - assert_match(/#{params.first['args'][2]}/, last_response.body) - end + assert_equal 3, Sidekiq::DeadSet.new.size + post "/morgue/all/delete", 'delete' => 'Delete' + assert_equal 0, Sidekiq::DeadSet.new.size + assert_equal 302, last_response.status + assert_equal 'http://example.org/morgue', last_response.header['Location'] + end - it 'handles bad query input' do - get '/queues/foo?page=B 'HardWorker', - 'args' => ['bob', 1, Time.now.to_f], - 'jid' => SecureRandom.hex(12) } - Sidekiq.redis do |conn| - conn.zadd('schedule', score, Sidekiq.dump_json(msg)) - end - [msg, score] - end - - def add_retry - msg = { 'class' => 'HardWorker', - 'args' => ['bob', 1, Time.now.to_f], - 'queue' => 'default', - 'error_message' => 'Some fake message', - 'error_class' => 'RuntimeError', - 'retry_count' => 0, - 'failed_at' => Time.now.to_f, - 'jid' => SecureRandom.hex(12) } - score = Time.now.to_f - Sidekiq.redis do |conn| - conn.zadd('retry', score, Sidekiq.dump_json(msg)) - end + it 'can retry a dead job' do + params = add_dead + post "/morgue/#{job_params(*params)}", 'retry' => 'Retry' + assert_equal 302, last_response.status + assert_equal 'http://example.org/morgue', last_response.header['Location'] + assert_equal 0, Sidekiq::DeadSet.new.size + + params = add_dead('jid-with-hyphen') + post "/morgue/#{job_params(*params)}", 'retry' => 'Retry' + assert_equal 302, last_response.status + assert_equal 0, Sidekiq::DeadSet.new.size - [msg, score] + get '/queues/foo' + assert_equal 200, last_response.status + assert_match(/#{params.first['args'][2]}/, last_response.body) end - def add_dead(jid = SecureRandom.hex(12)) - msg = { 'class' => 'HardWorker', - 'args' => ['bob', 1, Time.now.to_f], - 'queue' => 'foo', - 'error_message' => 'Some fake message', - 'error_class' => 'RuntimeError', - 'retry_count' => 0, - 'failed_at' => Time.now.utc, - 'jid' => jid } - score = Time.now.to_f - Sidekiq.redis do |conn| - conn.zadd('dead', score, Sidekiq.dump_json(msg)) - end - [msg, score] + it 'handles bad query input' do + get '/queues/foo?page=B 'FailWorker', - 'args' => ['hello'], - 'queue' => 'foo', - 'error_message' => 'fail message: hello', - 'error_class' => 'RuntimeError', - 'retry_count' => 0, - 'failed_at' => Time.now.to_f, - 'jid' => SecureRandom.hex(12) } - score = Time.now.to_f - Sidekiq.redis do |conn| - conn.zadd('retry', score, Sidekiq.dump_json(msg)) - end + def add_scheduled + score = Time.now.to_f + msg = { 'class' => 'HardWorker', + 'args' => ['bob', 1, Time.now.to_f], + 'jid' => SecureRandom.hex(12) } + Sidekiq.redis do |conn| + conn.zadd('schedule', score, Sidekiq.dump_json(msg)) + end + [msg, score] + end + + def add_retry + msg = { 'class' => 'HardWorker', + 'args' => ['bob', 1, Time.now.to_f], + 'queue' => 'default', + 'error_message' => 'Some fake message', + 'error_class' => 'RuntimeError', + 'retry_count' => 0, + 'failed_at' => Time.now.to_f, + 'jid' => SecureRandom.hex(12) } + score = Time.now.to_f + Sidekiq.redis do |conn| + conn.zadd('retry', score, Sidekiq.dump_json(msg)) + end + + [msg, score] + end + + def add_dead(jid = SecureRandom.hex(12)) + msg = { 'class' => 'HardWorker', + 'args' => ['bob', 1, Time.now.to_f], + 'queue' => 'foo', + 'error_message' => 'Some fake message', + 'error_class' => 'RuntimeError', + 'retry_count' => 0, + 'failed_at' => Time.now.utc, + 'jid' => jid } + score = Time.now.to_f + Sidekiq.redis do |conn| + conn.zadd('dead', score, Sidekiq.dump_json(msg)) + end + [msg, score] + end - [msg, score] + def kill_bad + job = "{ something bad }" + score = Time.now.to_f + Sidekiq.redis do |conn| + conn.zadd('dead', score, job) end + [job, score] + end - def add_worker - key = "#{hostname}:#{$$}" - msg = "{\"queue\":\"default\",\"payload\":{\"retry\":true,\"queue\":\"default\",\"timeout\":20,\"backtrace\":5,\"class\":\"HardWorker\",\"args\":[\"bob\",10,5],\"jid\":\"2b5ad2b016f5e063a1c62872\"},\"run_at\":1361208995}" - Sidekiq.redis do |conn| - conn.multi do - conn.sadd("processes", key) - conn.hmset(key, 'info', Sidekiq.dump_json('hostname' => 'foo', 'started_at' => Time.now.to_f, "queues" => []), 'at', Time.now.to_f, 'busy', 4) - conn.hmset("#{key}:workers", Time.now.to_f, msg) - end + def add_xss_retry(job_id=SecureRandom.hex(12)) + msg = { 'class' => 'FailWorker', + 'args' => ['hello'], + 'queue' => 'foo', + 'error_message' => 'fail message: hello', + 'error_class' => 'RuntimeError', + 'retry_count' => 0, + 'failed_at' => Time.now.to_f, + 'jid' => SecureRandom.hex(12) } + score = Time.now.to_f + Sidekiq.redis do |conn| + conn.zadd('retry', score, Sidekiq.dump_json(msg)) + end + + [msg, score] + end + + def add_worker + key = "#{hostname}:#{$$}" + msg = "{\"queue\":\"default\",\"payload\":{\"retry\":true,\"queue\":\"default\",\"timeout\":20,\"backtrace\":5,\"class\":\"HardWorker\",\"args\":[\"bob\",10,5],\"jid\":\"2b5ad2b016f5e063a1c62872\"},\"run_at\":1361208995}" + Sidekiq.redis do |conn| + conn.multi do + conn.sadd("processes", key) + conn.hmset(key, 'info', Sidekiq.dump_json('hostname' => 'foo', 'started_at' => Time.now.to_f, "queues" => []), 'at', Time.now.to_f, 'busy', 4) + conn.hmset("#{key}:workers", Time.now.to_f, msg) end end end +end - describe 'sidekiq web with basic auth' do - include Rack::Test::Methods +describe 'sidekiq web with basic auth' do + include Rack::Test::Methods - def app - app = Sidekiq::Web.new - app.use(Rack::Auth::Basic) { |user, pass| user == "a" && pass == "b" } + def app + app = Sidekiq::Web.new + app.use(Rack::Auth::Basic) { |user, pass| user == "a" && pass == "b" } - app - end + app + end - it 'requires basic authentication' do - get '/' + it 'requires basic authentication' do + get '/' - assert_equal 401, last_response.status - refute_nil last_response.header["WWW-Authenticate"] - end + assert_equal 401, last_response.status + refute_nil last_response.header["WWW-Authenticate"] + end - it 'authenticates successfuly' do - basic_authorize 'a', 'b' + it 'authenticates successfuly' do + basic_authorize 'a', 'b' - get '/' + get '/' - assert_equal 200, last_response.status - end + assert_equal 200, last_response.status end +end - describe 'sidekiq web with custom session' do - include Rack::Test::Methods +describe 'sidekiq web with custom session' do + include Rack::Test::Methods - def app - app = Sidekiq::Web.new + def app + app = Sidekiq::Web.new - app.use Rack::Session::Cookie, secret: 'v3rys3cr31', host: 'nicehost.org' + app.use Rack::Session::Cookie, secret: 'v3rys3cr31', host: 'nicehost.org' - app - end + app + end - it 'requires basic authentication' do - get '/' + it 'requires basic authentication' do + get '/' - session_options = last_request.env['rack.session'].options + session_options = last_request.env['rack.session'].options - assert_equal 'v3rys3cr31', session_options[:secret] - assert_equal 'nicehost.org', session_options[:host] - end + assert_equal 'v3rys3cr31', session_options[:secret] + assert_equal 'nicehost.org', session_options[:host] end - describe 'sidekiq web sessions options' do + describe 'sessions options' do include Rack::Test::Methods describe 'using #disable' do diff --git a/test/test_web_helpers.rb b/test/test_web_helpers.rb index a95d6b2fb..541ade247 100644 --- a/test/test_web_helpers.rb +++ b/test/test_web_helpers.rb @@ -3,7 +3,7 @@ require 'sidekiq/web' class TestWebHelpers < Minitest::Test - +raise 'boom' class Helpers include Sidekiq::WebHelpers From 4f057c947de40af27f949aa4e59f28654b70e4b9 Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Thu, 28 Feb 2019 12:57:29 -0800 Subject: [PATCH 09/13] oops --- test/test_web_helpers.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_web_helpers.rb b/test/test_web_helpers.rb index 541ade247..1e8dd41fa 100644 --- a/test/test_web_helpers.rb +++ b/test/test_web_helpers.rb @@ -3,7 +3,6 @@ require 'sidekiq/web' class TestWebHelpers < Minitest::Test -raise 'boom' class Helpers include Sidekiq::WebHelpers From cfe53e5fadfe88c53b62608d1dfc75ecf2ac18a0 Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Thu, 28 Feb 2019 14:05:45 -0800 Subject: [PATCH 10/13] Allow tests to override enqueued_at --- lib/sidekiq/client.rb | 2 +- test/test_web.rb | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/sidekiq/client.rb b/lib/sidekiq/client.rb index 5ec4f0b7f..b7f5f482d 100644 --- a/lib/sidekiq/client.rb +++ b/lib/sidekiq/client.rb @@ -198,7 +198,7 @@ def atomic_push(conn, payloads) q = payloads.first['queue'] now = Time.now.to_f to_push = payloads.map do |entry| - entry['enqueued_at'] = now + entry['enqueued_at'] ||= now Sidekiq.dump_json(entry) end conn.sadd('queues', q) diff --git a/test/test_web.rb b/test/test_web.rb index 0716d2571..78cfa8c03 100644 --- a/test/test_web.rb +++ b/test/test_web.rb @@ -105,13 +105,22 @@ def perform(a, b) end it 'can display queues' do - assert Sidekiq::Client.push('queue' => :foo, 'class' => WebWorker, 'args' => [1, 3], 'enqueued_at' => Time.now.to_f - 65) + assert Sidekiq::Client.push('queue' => :foo, 'class' => WebWorker, 'args' => [1, 3]) get '/queues' assert_equal 200, last_response.status assert_match(/foo/, last_response.body) refute_match(/HardWorker/, last_response.body) - assert_match(/seconds ago/, last_response.body) + refute_match(/datetime/, last_response.body) + Sidekiq::Queue.new("foo").clear + + assert Sidekiq::Client.push('queue' => :foo, 'class' => WebWorker, 'args' => [1, 3], 'enqueued_at' => Time.now.to_f - 65) + + get '/queues' + assert_equal 200, last_response.status + assert_match(/foo/, last_response.body) + refute_match(/WebWorker/, last_response.body) + assert_match(/datetime/, last_response.body) end it 'handles queue view' do From 20f4cdb4ff422b55e0d00d9e003a0b17f821c6f8 Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Thu, 28 Feb 2019 14:07:27 -0800 Subject: [PATCH 11/13] Fix various flaky tests due to process-wide data changes --- test/test_util.rb | 18 ++++-- test/test_web.rb | 160 +++++++++++++++++++++++----------------------- 2 files changed, 91 insertions(+), 87 deletions(-) diff --git a/test/test_util.rb b/test/test_util.rb index 43e980616..9b1b9c2cc 100644 --- a/test/test_util.rb +++ b/test/test_util.rb @@ -3,19 +3,23 @@ require 'sidekiq/util' class TestUtil < Minitest::Test - class Helpers include Sidekiq::Util end def test_event_firing - Sidekiq.options[:lifecycle_events][:startup] = [proc { raise "boom" }] - h = Helpers.new - h.fire_event(:startup) + before_handlers = Sidekiq.options[:lifecycle_events][:startup] + begin + Sidekiq.options[:lifecycle_events][:startup] = [proc { raise "boom" }] + h = Helpers.new + h.fire_event(:startup) - Sidekiq.options[:lifecycle_events][:startup] = [proc { raise "boom" }] - assert_raises RuntimeError do - h.fire_event(:startup, reraise: true) + Sidekiq.options[:lifecycle_events][:startup] = [proc { raise "boom" }] + assert_raises RuntimeError do + h.fire_event(:startup, reraise: true) + end + ensure + Sidekiq.options[:lifecycle_events][:startup] = before_handlers end end end diff --git a/test/test_web.rb b/test/test_web.rb index 78cfa8c03..5cdd4adfc 100644 --- a/test/test_web.rb +++ b/test/test_web.rb @@ -17,7 +17,9 @@ def job_params(job, score) end before do + ENV["RACK_ENV"] = "test" Sidekiq.redis {|c| c.flushdb } + Sidekiq::Web.middlewares.clear end class WebWorker @@ -634,110 +636,108 @@ def add_worker end end end -end - -describe 'sidekiq web with basic auth' do - include Rack::Test::Methods - - def app - app = Sidekiq::Web.new - app.use(Rack::Auth::Basic) { |user, pass| user == "a" && pass == "b" } - - app - end - - it 'requires basic authentication' do - get '/' - assert_equal 401, last_response.status - refute_nil last_response.header["WWW-Authenticate"] - end + describe 'basic auth' do + include Rack::Test::Methods - it 'authenticates successfuly' do - basic_authorize 'a', 'b' + def app + app = Sidekiq::Web.new + app.use(Rack::Auth::Basic) { |user, pass| user == "a" && pass == "b" } - get '/' + app + end - assert_equal 200, last_response.status - end -end + it 'requires basic authentication' do + get '/' -describe 'sidekiq web with custom session' do - include Rack::Test::Methods + assert_equal 401, last_response.status + refute_nil last_response.header["WWW-Authenticate"] + end - def app - app = Sidekiq::Web.new + it 'authenticates successfuly' do + basic_authorize 'a', 'b' - app.use Rack::Session::Cookie, secret: 'v3rys3cr31', host: 'nicehost.org' + get '/' - app + assert_equal 200, last_response.status + end end - it 'requires basic authentication' do - get '/' - - session_options = last_request.env['rack.session'].options + describe 'custom session' do + include Rack::Test::Methods - assert_equal 'v3rys3cr31', session_options[:secret] - assert_equal 'nicehost.org', session_options[:host] - end + def app + app = Sidekiq::Web.new + app.use Rack::Session::Cookie, secret: 'v3rys3cr31', host: 'nicehost.org' + app + end - describe 'sessions options' do - include Rack::Test::Methods + it 'requires basic authentication' do + get '/' - describe 'using #disable' do - def app - app = Sidekiq::Web.new - app.disable(:sessions) - app - end + session_options = last_request.env['rack.session'].options - it "doesn't create sessions" do - get '/' - assert_nil last_request.env['rack.session'] - end + assert_equal 'v3rys3cr31', session_options[:secret] + assert_equal 'nicehost.org', session_options[:host] end - describe 'using #set with false argument' do - def app - app = Sidekiq::Web.new - app.set(:sessions, false) - app - end + describe 'sessions options' do + include Rack::Test::Methods - it "doesn't create sessions" do - get '/' - assert_nil last_request.env['rack.session'] - end - end + describe 'using #disable' do + def app + app = Sidekiq::Web.new + app.disable(:sessions) + app + end - describe 'using #set with an hash' do - def app - app = Sidekiq::Web.new - app.set(:sessions, { domain: :all }) - app + it "doesn't create sessions" do + get '/' + assert_nil last_request.env['rack.session'] + end end - it "creates sessions" do - get '/' - refute_nil last_request.env['rack.session'] - refute_empty last_request.env['rack.session'].options - assert_equal :all, last_request.env['rack.session'].options[:domain] + describe 'using #set with false argument' do + def app + app = Sidekiq::Web.new + app.set(:sessions, false) + app + end + + it "doesn't create sessions" do + get '/' + assert_nil last_request.env['rack.session'] + end end - end - describe 'using #enable' do - def app - app = Sidekiq::Web.new - app.enable(:sessions) - app + describe 'using #set with an hash' do + def app + app = Sidekiq::Web.new + app.set(:sessions, { domain: :all }) + app + end + + it "creates sessions" do + get '/' + refute_nil last_request.env['rack.session'] + refute_empty last_request.env['rack.session'].options + assert_equal :all, last_request.env['rack.session'].options[:domain] + end end - it "creates sessions" do - get '/' - refute_nil last_request.env['rack.session'] - refute_empty last_request.env['rack.session'].options - refute_nil last_request.env['rack.session'].options[:secret] + describe 'using #enable' do + def app + app = Sidekiq::Web.new + app.enable(:sessions) + app + end + + it "creates sessions" do + get '/' + refute_nil last_request.env['rack.session'] + refute_empty last_request.env['rack.session'].options + refute_nil last_request.env['rack.session'].options[:secret] + end end end end From 4d883194350cf9202ff5c1650730dfa5ee3d2e06 Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Thu, 28 Feb 2019 14:36:40 -0800 Subject: [PATCH 12/13] Refactor sidekiqctl so it can be tested easily --- bin/sidekiqctl | 239 ++-------------------------------------- lib/sidekiq/ctl.rb | 221 +++++++++++++++++++++++++++++++++++++ test/test_sidekiqctl.rb | 10 +- 3 files changed, 235 insertions(+), 235 deletions(-) create mode 100644 lib/sidekiq/ctl.rb diff --git a/bin/sidekiqctl b/bin/sidekiqctl index 297b34963..d5f9a42d5 100755 --- a/bin/sidekiqctl +++ b/bin/sidekiqctl @@ -2,236 +2,19 @@ require 'fileutils' require 'sidekiq/api' - -class Sidekiqctl - DEFAULT_KILL_TIMEOUT = 10 - CMD = File.basename($0) - - attr_reader :stage, :pidfile, :kill_timeout - - def self.print_usage - puts "#{CMD} - control Sidekiq from the command line." - puts - puts "Usage: #{CMD} quiet " - puts " #{CMD} stop " - puts " #{CMD} status
" - puts - puts " is path to a pidfile" - puts " is number of seconds to wait until Sidekiq exits" - puts " (default: #{Sidekiqctl::DEFAULT_KILL_TIMEOUT}), after which Sidekiq will be KILL'd" - puts - puts "
(optional) view a specific section of the status output" - puts " Valid sections are: #{Sidekiqctl::Status::VALID_SECTIONS.join(', ')}" - puts - puts "Be sure to set the kill_timeout LONGER than Sidekiq's -t timeout. If you want" - puts "to wait 60 seconds for jobs to finish, use `sidekiq -t 60` and `sidekiqctl stop" - puts " path_to_pidfile 61`" - puts - end - - def initialize(stage, pidfile, timeout) - @stage = stage - @pidfile = pidfile - @kill_timeout = timeout - - done('No pidfile given', :error) if !pidfile - done("Pidfile #{pidfile} does not exist", :warn) if !File.exist?(pidfile) - done('Invalid pidfile content', :error) if pid == 0 - - fetch_process - - begin - send(stage) - rescue NoMethodError - done "Invalid command: #{stage}", :error - end - end - - def fetch_process - Process.kill(0, pid) - rescue Errno::ESRCH - done "Process doesn't exist", :error - # We were not allowed to send a signal, but the process must have existed - # when Process.kill() was called. - rescue Errno::EPERM - return pid - end - - def done(msg, error = nil) - puts msg - exit(exit_signal(error)) - end - - def exit_signal(error) - (error == :error) ? 1 : 0 - end - - def pid - @pid ||= File.read(pidfile).to_i - end - - def quiet - `kill -TSTP #{pid}` - end - - def stop - `kill -TERM #{pid}` - kill_timeout.times do - begin - Process.kill(0, pid) - rescue Errno::ESRCH - FileUtils.rm_f pidfile - done 'Sidekiq shut down gracefully.' - rescue Errno::EPERM - done 'Not permitted to shut down Sidekiq.' - end - sleep 1 - end - `kill -9 #{pid}` - FileUtils.rm_f pidfile - done 'Sidekiq shut down forcefully.' - end - alias_method :shutdown, :stop - - class Status - VALID_SECTIONS = %w[all version overview processes queues] - def display(section = nil) - section ||= 'all' - unless VALID_SECTIONS.include? section - puts "I don't know how to check the status of '#{section}'!" - puts "Try one of these: #{VALID_SECTIONS.join(', ')}" - return - end - send(section) - rescue StandardError => e - puts "Couldn't get status: #{e}" - end - - def all - version - puts - overview - puts - processes - puts - queues - end - - def version - puts "Sidekiq #{Sidekiq::VERSION}" - puts Time.now - end - - def overview - puts '---- Overview ----' - puts " Processed: #{delimit stats.processed}" - puts " Failed: #{delimit stats.failed}" - puts " Busy: #{delimit stats.workers_size}" - puts " Enqueued: #{delimit stats.enqueued}" - puts " Retries: #{delimit stats.retry_size}" - puts " Scheduled: #{delimit stats.scheduled_size}" - puts " Dead: #{delimit stats.dead_size}" - end - - def processes - puts "---- Processes (#{process_set.size}) ----" - process_set.each_with_index do |process, index| - puts "#{process['identity']} #{tags_for(process)}" - puts " Started: #{Time.at(process['started_at'])} (#{time_ago(process['started_at'])})" - puts " Threads: #{process['concurrency']} (#{process['busy']} busy)" - puts " Queues: #{split_multiline(process['queues'].sort, pad: 11)}" - puts '' unless (index+1) == process_set.size - end - end - - COL_PAD = 2 - def queues - puts "---- Queues (#{queue_data.size}) ----" - columns = { - name: [:ljust, (['name'] + queue_data.map(&:name)).map(&:length).max + COL_PAD], - size: [:rjust, (['size'] + queue_data.map(&:size)).map(&:length).max + COL_PAD], - latency: [:rjust, (['latency'] + queue_data.map(&:latency)).map(&:length).max + COL_PAD] - } - columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) } - puts - queue_data.each do |q| - columns.each do |col, (dir, width)| - print q.send(col).public_send(dir, width) - end - puts - end - end - - private - - def delimit(number) - number.to_s.reverse.scan(/.{1,3}/).join(',').reverse - end - - def split_multiline(values, opts = {}) - return 'none' unless values - pad = opts[:pad] || 0 - max_length = opts[:max_length] || (80 - pad) - out = [] - line = '' - values.each do |value| - if (line.length + value.length) > max_length - out << line - line = ' ' * pad - end - line << value + ', ' - end - out << line[0..-3] - out.join("\n") - end - - def tags_for(process) - tags = [ - process['tag'], - process['labels'], - (process['quiet'] == 'true' ? 'quiet' : nil) - ].flatten.compact - tags.any? ? "[#{tags.join('] [')}]" : nil - end - - def time_ago(timestamp) - seconds = Time.now - Time.at(timestamp) - return 'just now' if seconds < 60 - return 'a minute ago' if seconds < 120 - return "#{seconds.floor / 60} minutes ago" if seconds < 3600 - return 'an hour ago' if seconds < 7200 - "#{seconds.floor / 60 / 60} hours ago" - end - - QUEUE_STRUCT = Struct.new(:name, :size, :latency) - def queue_data - @queue_data ||= Sidekiq::Queue.all.map do |q| - QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf('%#.2f', q.latency)) - end - end - - def process_set - @process_set ||= Sidekiq::ProcessSet.new - end - - def stats - @stats ||= Sidekiq::Stats.new - end - end -end +require 'sidekiq/ctl' if ARGV[0] == 'status' - Sidekiqctl::Status.new.display(ARGV[1]) - exit -end - -if ARGV.length < 2 - Sidekiqctl.print_usage + Sidekiq::Ctl::Status.new.display(ARGV[1]) else - stage = ARGV[0] - pidfile = ARGV[1] - timeout = ARGV[2].to_i - timeout = Sidekiqctl::DEFAULT_KILL_TIMEOUT if timeout == 0 + if ARGV.length < 2 + Sidekiq::Ctl.print_usage + else + stage = ARGV[0] + pidfile = ARGV[1] + timeout = ARGV[2].to_i + timeout = Sidekiq::Ctl::DEFAULT_KILL_TIMEOUT if timeout == 0 - Sidekiqctl.new(stage, pidfile, timeout) + Sidekiq::Ctl.new(stage, pidfile, timeout) + end end diff --git a/lib/sidekiq/ctl.rb b/lib/sidekiq/ctl.rb new file mode 100644 index 000000000..ffcf6590c --- /dev/null +++ b/lib/sidekiq/ctl.rb @@ -0,0 +1,221 @@ +#!/usr/bin/env ruby + +require 'fileutils' +require 'sidekiq/api' + +class Sidekiq::Ctl + DEFAULT_KILL_TIMEOUT = 10 + CMD = File.basename($0) + + attr_reader :stage, :pidfile, :kill_timeout + + def self.print_usage + puts "#{CMD} - control Sidekiq from the command line." + puts + puts "Usage: #{CMD} quiet " + puts " #{CMD} stop " + puts " #{CMD} status
" + puts + puts " is path to a pidfile" + puts " is number of seconds to wait until Sidekiq exits" + puts " (default: #{Sidekiqctl::DEFAULT_KILL_TIMEOUT}), after which Sidekiq will be KILL'd" + puts + puts "
(optional) view a specific section of the status output" + puts " Valid sections are: #{Sidekiqctl::Status::VALID_SECTIONS.join(', ')}" + puts + puts "Be sure to set the kill_timeout LONGER than Sidekiq's -t timeout. If you want" + puts "to wait 60 seconds for jobs to finish, use `sidekiq -t 60` and `sidekiqctl stop" + puts " path_to_pidfile 61`" + puts + end + + def initialize(stage, pidfile, timeout) + @stage = stage + @pidfile = pidfile + @kill_timeout = timeout + + done('No pidfile given', :error) if !pidfile + done("Pidfile #{pidfile} does not exist", :warn) if !File.exist?(pidfile) + done('Invalid pidfile content', :error) if pid == 0 + + fetch_process + + begin + send(stage) + rescue NoMethodError + done "Invalid command: #{stage}", :error + end + end + + def fetch_process + Process.kill(0, pid) + rescue Errno::ESRCH + done "Process doesn't exist", :error + # We were not allowed to send a signal, but the process must have existed + # when Process.kill() was called. + rescue Errno::EPERM + return pid + end + + def done(msg, error = nil) + puts msg + exit(exit_signal(error)) + end + + def exit_signal(error) + (error == :error) ? 1 : 0 + end + + def pid + @pid ||= File.read(pidfile).to_i + end + + def quiet + `kill -TSTP #{pid}` + end + + def stop + `kill -TERM #{pid}` + kill_timeout.times do + begin + Process.kill(0, pid) + rescue Errno::ESRCH + FileUtils.rm_f pidfile + done 'Sidekiq shut down gracefully.' + rescue Errno::EPERM + done 'Not permitted to shut down Sidekiq.' + end + sleep 1 + end + `kill -9 #{pid}` + FileUtils.rm_f pidfile + done 'Sidekiq shut down forcefully.' + end + alias_method :shutdown, :stop + + class Status + VALID_SECTIONS = %w[all version overview processes queues] + def display(section = nil) + section ||= 'all' + unless VALID_SECTIONS.include? section + puts "I don't know how to check the status of '#{section}'!" + puts "Try one of these: #{VALID_SECTIONS.join(', ')}" + return + end + send(section) + rescue StandardError => e + puts "Couldn't get status: #{e}" + end + + def all + version + puts + overview + puts + processes + puts + queues + end + + def version + puts "Sidekiq #{Sidekiq::VERSION}" + puts Time.now + end + + def overview + puts '---- Overview ----' + puts " Processed: #{delimit stats.processed}" + puts " Failed: #{delimit stats.failed}" + puts " Busy: #{delimit stats.workers_size}" + puts " Enqueued: #{delimit stats.enqueued}" + puts " Retries: #{delimit stats.retry_size}" + puts " Scheduled: #{delimit stats.scheduled_size}" + puts " Dead: #{delimit stats.dead_size}" + end + + def processes + puts "---- Processes (#{process_set.size}) ----" + process_set.each_with_index do |process, index| + puts "#{process['identity']} #{tags_for(process)}" + puts " Started: #{Time.at(process['started_at'])} (#{time_ago(process['started_at'])})" + puts " Threads: #{process['concurrency']} (#{process['busy']} busy)" + puts " Queues: #{split_multiline(process['queues'].sort, pad: 11)}" + puts '' unless (index+1) == process_set.size + end + end + + COL_PAD = 2 + def queues + puts "---- Queues (#{queue_data.size}) ----" + columns = { + name: [:ljust, (['name'] + queue_data.map(&:name)).map(&:length).max + COL_PAD], + size: [:rjust, (['size'] + queue_data.map(&:size)).map(&:length).max + COL_PAD], + latency: [:rjust, (['latency'] + queue_data.map(&:latency)).map(&:length).max + COL_PAD] + } + columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) } + puts + queue_data.each do |q| + columns.each do |col, (dir, width)| + print q.send(col).public_send(dir, width) + end + puts + end + end + + private + + def delimit(number) + number.to_s.reverse.scan(/.{1,3}/).join(',').reverse + end + + def split_multiline(values, opts = {}) + return 'none' unless values + pad = opts[:pad] || 0 + max_length = opts[:max_length] || (80 - pad) + out = [] + line = '' + values.each do |value| + if (line.length + value.length) > max_length + out << line + line = ' ' * pad + end + line << value + ', ' + end + out << line[0..-3] + out.join("\n") + end + + def tags_for(process) + tags = [ + process['tag'], + process['labels'], + (process['quiet'] == 'true' ? 'quiet' : nil) + ].flatten.compact + tags.any? ? "[#{tags.join('] [')}]" : nil + end + + def time_ago(timestamp) + seconds = Time.now - Time.at(timestamp) + return 'just now' if seconds < 60 + return 'a minute ago' if seconds < 120 + return "#{seconds.floor / 60} minutes ago" if seconds < 3600 + return 'an hour ago' if seconds < 7200 + "#{seconds.floor / 60 / 60} hours ago" + end + + QUEUE_STRUCT = Struct.new(:name, :size, :latency) + def queue_data + @queue_data ||= Sidekiq::Queue.all.map do |q| + QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf('%#.2f', q.latency)) + end + end + + def process_set + @process_set ||= Sidekiq::ProcessSet.new + end + + def stats + @stats ||= Sidekiq::Stats.new + end + end +end diff --git a/test/test_sidekiqctl.rb b/test/test_sidekiqctl.rb index 3d2efafdf..693031895 100644 --- a/test/test_sidekiqctl.rb +++ b/test/test_sidekiqctl.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true require_relative 'helper' +require 'sidekiq/ctl' def capture_stdout $stdout = StringIO.new @@ -9,18 +10,13 @@ def capture_stdout $stdout = STDOUT end -capture_stdout do - ARGV = %w[status] - load 'bin/sidekiqctl' -end - def output(section = 'all') capture_stdout do - Sidekiqctl::Status.new.display(section) + Sidekiq::Ctl::Status.new.display(section) end end -describe Sidekiqctl do +describe Sidekiq::Ctl do describe 'status' do describe 'version' do it 'displays the current Sidekiq version' do From edbefc4ec1ea4cd3eb738713f6f940de142f3787 Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Fri, 1 Mar 2019 13:27:28 -0800 Subject: [PATCH 13/13] flush redis so status is predictable --- test/test_sidekiqctl.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_sidekiqctl.rb b/test/test_sidekiqctl.rb index 693031895..bf8eb2e8c 100644 --- a/test/test_sidekiqctl.rb +++ b/test/test_sidekiqctl.rb @@ -17,6 +17,10 @@ def output(section = 'all') end describe Sidekiq::Ctl do + before do + Sidekiq.redis {|c| c.flushdb} + end + describe 'status' do describe 'version' do it 'displays the current Sidekiq version' do